rn-marquee-text 2.1.1-beta.1 → 3.0.0

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/README.md CHANGED
@@ -1,25 +1,19 @@
1
- # React Native Marquee Text 🏃‍♂️
1
+ ```markdown
2
+ # React Native Marquee Text 🚀
2
3
 
3
- [![npm version](https://img.shields.io/npm/v/rn-marquee-text?style=flat-square)](https://www.npmjs.com/package/rn-marquee-text)
4
- [![license](https://img.shields.io/npm/l/rn-marquee-text?style=flat-square)](https://github.com/yourusername/rn-marquee-text/blob/main/LICENSE)
5
- [![downloads](https://img.shields.io/npm/dt/rn-marquee-text?style=flat-square)](https://www.npmjs.com/package/rn-marquee-text)
6
- [![CI Status](https://img.shields.io/github/actions/workflow/status/yourusername/rn-marquee-text/ci.yml?style=flat-square)](https://github.com/yourusername/rn-marquee-text/actions)
7
-
8
- A high-performance, customizable marquee component for React Native with smooth animations and gesture support.
9
-
10
- ![Demo](https://github.com/yourusername/rn-marquee-text/blob/main/assets/demo.gif?raw=true)
4
+ ![Horizontal and Vertical Marquee Demo](demo-combined.gif)
11
5
 
12
6
  ## Features ✨
13
7
 
14
- - 🎭 Multiple animation modes (loop, bounce)
15
- - ↔️ Bidirectional scrolling (horizontal/vertical)
16
- - Touch gesture interaction
17
- - Optimized with Reanimated 2
18
- - 🎨 Customizable speed, spacing, and delays
19
- - 📱 Supports both iOS and Android
20
- - 🛠 TypeScript support included
8
+ - **Multi-directional scrolling**: Horizontal and vertical marquee effects
9
+ - **Animation modes**: Loop and bounce animations
10
+ - **Performance optimized**: Built with React Native Reanimated 2
11
+ - **Customizable**: Control speed, delay, spacing, and more
12
+ - **Interactive**: Gesture support for pausing/resuming
13
+ - **Flexible content**: Supports both text and custom components
14
+ - **TypeScript ready**: Complete type definitions included
21
15
 
22
- ## Installation 💻
16
+ ## Installation 📦
23
17
 
24
18
  ```bash
25
19
  # Using npm
@@ -29,188 +23,191 @@ npm install rn-marquee-text react-native-reanimated react-native-gesture-handler
29
23
  yarn add rn-marquee-text react-native-reanimated react-native-gesture-handler
30
24
  ```
31
25
 
32
- ### Peer Dependencies Setup
33
- - Follow [Reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation)
34
- - Configure [Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/installation)
26
+ ### Peer Dependencies
27
+ Ensure you've properly installed and configured:
28
+ - [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation)
29
+ - [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/)
35
30
 
36
- ## Basic Usage 🚀
31
+ ## Usage 🚀
37
32
 
33
+ ### Basic Implementation
38
34
  ```jsx
39
- import Marquee from 'rn-marquee-text';
40
-
41
- function App() {
42
- return (
43
- <Marquee.MarqueeText
44
- speed={50}
45
- style={{ height: 40 }}
46
- textStyle={{ fontSize: 16 }}
35
+ import React from 'react';
36
+ import { StyleSheet, View } from 'react-native';
37
+ import MarqueeText from 'rn-marquee-text';
38
+
39
+ const App = () => (
40
+ <View style={styles.container}>
41
+ <MarqueeText
42
+ style={styles.marquee}
43
+ speed={40}
44
+ textStyle={styles.text}
47
45
  >
48
- This text will scroll automatically!
49
- </Marquee.MarqueeText>
50
- );
51
- }
52
- ```
53
-
54
- ## Advanced Usage 🧠
46
+ Your scrolling text goes here
47
+ </MarqueeText>
48
+ </View>
49
+ );
55
50
 
56
- ### Custom Animation Configuration
57
- ```jsx
58
- <Marquee.MarqueeText
59
- mode="bounce"
60
- direction="vertical"
61
- speed={30}
62
- delay={2000}
63
- endPauseDuration={1500}
64
- spacing={30}
65
- >
66
- {yourCustomComponent}
67
- </Marquee.MarqueeText>
68
- ```
51
+ const styles = StyleSheet.create({
52
+ container: { flex: 1, justifyContent: 'center', padding: 20 },
53
+ marquee: { height: 50, backgroundColor: '#f5f5f5', borderRadius: 8 },
54
+ text: { fontSize: 16, color: '#333' }
55
+ });
69
56
 
70
- ### Imperative Control
71
- ```jsx
72
- const marqueeRef = useRef(null);
73
-
74
- // Start/pause programmatically
75
- <>
76
- <Marquee.MarqueeText ref={marqueeRef} />
77
- <Button
78
- title="Toggle"
79
- onPress={() => marqueeRef.current?.isActive
80
- ? marqueeRef.current.stop()
81
- : marqueeRef.current.start()
82
- }
83
- />
84
- </>
57
+ export default App;
85
58
  ```
86
59
 
87
60
  ## API Reference 📚
88
61
 
89
62
  ### Props
90
-
91
63
  | Prop | Type | Default | Description |
92
64
  |------|------|---------|-------------|
93
- | `mode` | `'loop' \| 'bounce'` | `'loop'` | Animation behavior |
94
- | `speed` | `number` | `30` | Scroll speed (px/sec) |
95
- | `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | Scroll axis |
96
- | `delay` | `number` | `1500` | Initial delay (ms) |
97
- | `endPauseDuration` | `number` | `1000` | Pause duration at end (bounce mode) |
98
- | `spacing` | `number` | `20` | Space between clones |
99
- | `enabled` | `boolean` | `true` | Animation state |
100
- | `withGesture` | `boolean` | `true` | Enable gesture interaction |
101
- | `reverse` | `boolean` | `false` | Reverse animation direction |
102
- | `backgroundColor` | `string` | `'transparent'` | Container background color |
103
- | `frameRate` | `number` | `60` | Animation frame rate |
104
- | `style` | `ViewStyle` | - | Container style |
105
- | `textStyle` | `TextStyle` | - | Text style (when children is string) |
106
- | `onAnimationStart` | `() => void` | - | Animation start callback |
107
- | `onAnimationStop` | `() => void` | - | Animation stop callback |
65
+ | `children` | React.ReactNode | Required | Content to scroll (string or components) |
66
+ | `mode` | 'loop' \| 'bounce' | 'loop' | Animation behavior |
67
+ | `speed` | number | 30 | Pixels per second |
68
+ | `direction` | 'horizontal' \| 'vertical' | 'horizontal' | Scroll direction |
69
+ | `enabled` | boolean | true | Animation active state |
70
+ | `delay` | number | 1500 | Initial delay (ms) before starting |
71
+ | `endPauseDuration` | number | 1000 | Pause at end (bounce mode only) |
72
+ | `gap` | number | 20 | Gap between repeating text loops |
73
+ | `spacing` | number | 20 | **Deprecated**: Use `gap` instead |
74
+ | `reverse` | boolean | false | Reverse the animation direction |
75
+ | `backgroundColor` | string | 'transparent' | Background color for the container |
76
+ | `withGesture` | boolean | true | Allow user interaction with gesture handling |
77
+ | `frameRate` | number | - | Custom frame rate for animation |
78
+ | `onAnimationStart` | function | - | Callback when animation starts |
79
+ | `onAnimationStop` | function | - | Callback when animation stops |
80
+ | `style` | ViewStyle | - | Container style |
81
+ | `textStyle` | TextStyle | - | Text style (when children is a string) |
108
82
 
109
83
  ### Methods (via ref)
84
+ ```jsx
85
+ const marqueeRef = useRef();
110
86
 
111
- ```typescript
112
- interface MarqueeTextRef {
113
- start: () => void;
114
- stop: () => void;
115
- isActive: boolean;
116
- }
117
- ```
118
-
119
- ## Examples 🎨
87
+ // Start animation
88
+ marqueeRef.current?.start();
120
89
 
121
- ### News Ticker
122
- ```jsx
123
- <Marquee.MarqueeText
124
- style={styles.ticker}
125
- textStyle={styles.tickerText}
126
- speed={40}
127
- >
128
- BREAKING: React Native Marquee Text released! • New version available • Check out the docs
129
- </Marquee.MarqueeText>
90
+ // Stop animation
91
+ marqueeRef.current?.stop();
130
92
 
131
- const styles = StyleSheet.create({
132
- ticker: {
133
- backgroundColor: '#ff0000',
134
- height: 30,
135
- },
136
- tickerText: {
137
- color: 'white',
138
- fontSize: 14,
139
- fontWeight: 'bold',
140
- },
141
- });
93
+ // Check if active
94
+ const isActive = marqueeRef.current?.isActive;
142
95
  ```
143
96
 
144
- ### Vertical Product Carousel
145
- ```jsx
146
- <Marquee.MarqueeText
147
- direction="vertical"
148
- mode="bounce"
149
- speed={20}
150
- style={styles.carousel}
151
- >
152
- <ProductCard {...item1} />
153
- <ProductCard {...item2} />
154
- <ProductCard {...item3} />
155
- </Marquee.MarqueeText>
156
- ```
97
+ ## Examples 🎨
157
98
 
158
- ### Custom Component with High Speed
99
+ ### Comprehensive Demo Example
159
100
  ```jsx
160
- <Marquee.MarqueeText
161
- style={styles.marquee}
162
- speed={500}
163
- textStyle={styles.text}
164
- >
165
- Smarty was created by the creators of React Native.
166
- </Marquee.MarqueeText>
101
+ import React from 'react';
102
+ import { SafeAreaView, View, Text, StyleSheet } from 'react-native';
103
+ import { AutoScroll, Marquee } from 'rn-marquee-text';
104
+
105
+ export default function App() {
106
+ return (
107
+ <SafeAreaView style={{ flex: 1, padding: 16, gap: 24 }}>
108
+ <Text style={{ fontSize: 20, fontWeight: 'bold' }}>Marquee Text Demo</Text>
109
+
110
+ <View style={{ borderRadius: 8, overflow: 'hidden' }}>
111
+ <Marquee speed={80}>
112
+ This is a long scrolling text that demonstrates the marquee functionality
113
+ </Marquee>
114
+ </View>
115
+
116
+ <View style={{ borderRadius: 8, overflow: 'hidden' }}>
117
+ <Marquee
118
+ speed={120}
119
+ backgroundColor="#1a365d"
120
+ textStyle={{ color: '#f0f4f8', fontSize: 14 }}
121
+ >
122
+ Faster scrolling example with different colors
123
+ </Marquee>
124
+ </View>
125
+
126
+ <View style={{ width: '100%', borderRadius: 8, overflow: 'hidden' }}>
127
+ <Marquee
128
+ speed={100}
129
+ backgroundColor="#7b341e"
130
+ textStyle={{ color: '#fffaf0', fontSize: 18 }}
131
+ >
132
+ Breaking News: This is a full-width marquee text component that scrolls horizontally
133
+ </Marquee>
134
+ </View>
135
+
136
+ <View style={{ marginVertical: 10, borderRadius: 8, overflow: 'hidden' }}>
137
+ <Marquee
138
+ speed={50}
139
+ backgroundColor="#fff"
140
+ textStyle={{ color: '#0000ff', fontSize: 16 }}
141
+ >
142
+ This text will scroll horizontally with bounce effect. It will pause for 2 seconds at each end.
143
+ </Marquee>
144
+ </View>
145
+
146
+ <AutoScroll endPauseDuration={2000} style={styles.cardScroller} direction="horizontal">
147
+ <div style={styles.contentContainer}>
148
+ {[1, 2, 3, 4, 5].map((item) => (
149
+ <div key={item} style={styles.card}>
150
+ <Text style={styles.cardText}>Card {item}</Text>
151
+ </div>
152
+ ))}
153
+ </div>
154
+ </AutoScroll>
155
+ <AutoScroll endPauseDuration={2000} style={styles.cardScroller} direction="horizontal">
156
+ <Text style={styles.cardText}>This text will scroll horizontally with bounce effect and pause for 2 seconds at each end 🎉</Text>
157
+ </AutoScroll>
158
+ </SafeAreaView>
159
+ );
160
+ }
167
161
 
168
162
  const styles = StyleSheet.create({
169
- marquee: {
163
+ cardScroller: {
164
+ height: 120,
170
165
  width: '100%',
171
- height: 30,
166
+ marginTop: 10,
167
+ },
168
+ contentContainer: {
169
+ flexDirection: 'row',
170
+ padding: 10,
171
+ },
172
+ card: {
173
+ width: 100,
174
+ height: 100,
175
+ backgroundColor: '#f0f0f0',
176
+ borderRadius: 8,
177
+ marginRight: 10,
178
+ justifyContent: 'center',
179
+ alignItems: 'center',
180
+ },
181
+ cardText: {
182
+ fontSize: 16,
183
+ fontWeight: 'bold',
172
184
  },
173
- text: {
174
- fontSize: 16,
175
- color: '#333'
176
- }
177
185
  });
178
186
  ```
179
187
 
180
- ## Troubleshooting ⚠️
181
-
182
- ### Animation not smooth?
183
- - Ensure you're using Reanimated 2+
184
- - Check for heavy renders in parent components
185
- - Try reducing the `frameRate` prop
188
+ ## Troubleshooting 🛠️
186
189
 
187
- ### Text not visible?
188
- - Verify container has explicit dimensions
189
- - Check text color contrast
190
- - Ensure `textStyle` is properly applied
190
+ **Animation not working?**
191
+ - Verify Reanimated installation
192
+ - Check babel.config.js for Reanimated plugin
191
193
 
192
- ### Gesture not working?
193
- - Confirm Gesture Handler installation
194
- - Wrap root component with `GestureHandlerRootView`
195
- - Set `withGesture={true}` (default)
194
+ **Text not visible?**
195
+ - Ensure container has proper dimensions
196
+ - Verify text color contrasts with background
196
197
 
197
- ### Performance Issues?
198
- - Reduce `speed` for smoother animation
199
- - Minimize `spacing` to reduce clone count
200
- - Use `enabled={false}` when not needed
198
+ **Performance issues?**
199
+ - Reduce animation speed
200
+ - Simplify marquee content
201
+ - Use `frameRate` prop to limit FPS
201
202
 
202
203
  ## Contributing 🤝
203
-
204
- We welcome contributions! Please:
205
-
206
- 1. Fork the repository
207
- 2. Create a feature branch
208
- 3. Submit a pull request
204
+ Contributions are welcome! Please:
205
+ 1. Open an issue to discuss changes
206
+ 2. Ensure tests are updated
207
+ 3. Maintain consistent code style
209
208
 
210
209
  ## License 📄
210
+ MIT © PareshChavda(https://github.com/pareshchavda)
211
211
 
212
- MIT © PARESH CHAVDA
213
212
 
214
- ---
215
213
 
216
- Made with ❤️ for the React Native community
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
2
  import { TextStyle, ViewStyle } from 'react-native';
3
- import { AnimationMode } from './constants';
4
3
  /**
5
4
  * AutoScroll component that measures its content and container
6
5
  * dimensions and automatically sets up a scrolling animation if
@@ -16,9 +15,9 @@ import { AnimationMode } from './constants';
16
15
  * @prop {boolean} enabled - Whether the scrolling animation is enabled
17
16
  * @prop {'horizontal'|'vertical'} direction - The direction of the scrolling animation
18
17
  */
19
- declare const AutoScroll: ({ children, mode, speed, delay, endPauseDuration, style, textStyle, enabled, direction, }: {
18
+ export declare const AutoScroll: ({ children, mode, speed, delay, endPauseDuration, style, textStyle, enabled, direction, }: {
20
19
  children: React.ReactNode;
21
- mode?: AnimationMode;
20
+ mode?: "loop" | "bounce";
22
21
  speed?: number;
23
22
  delay?: number;
24
23
  endPauseDuration?: number;
@@ -27,4 +26,3 @@ declare const AutoScroll: ({ children, mode, speed, delay, endPauseDuration, sty
27
26
  enabled?: boolean;
28
27
  direction?: "horizontal" | "vertical";
29
28
  }) => React.JSX.Element;
30
- export default AutoScroll;
@@ -17,8 +17,8 @@ import { AnimationMode } from './constants';
17
17
  * @prop {boolean} enabled - Whether the scrolling animation is enabled
18
18
  * @prop {'horizontal'|'vertical'} direction - The direction of the scrolling animation
19
19
  */
20
- var AutoScroll = function (_a) {
21
- var children = _a.children, _b = _a.mode, mode = _b === void 0 ? AnimationMode.LOOP : _b, _c = _a.speed, speed = _c === void 0 ? 30 : _c, _d = _a.delay, delay = _d === void 0 ? 1500 : _d, _e = _a.endPauseDuration, endPauseDuration = _e === void 0 ? 1000 : _e, _f = _a.style, style = _f === void 0 ? {} : _f, _g = _a.textStyle, textStyle = _g === void 0 ? {} : _g, _h = _a.enabled, enabled = _h === void 0 ? true : _h, _j = _a.direction, direction = _j === void 0 ? 'vertical' : _j;
20
+ export var AutoScroll = function (_a) {
21
+ var children = _a.children, _b = _a.mode, mode = _b === void 0 ? 'loop' : _b, _c = _a.speed, speed = _c === void 0 ? 30 : _c, _d = _a.delay, delay = _d === void 0 ? 1500 : _d, _e = _a.endPauseDuration, endPauseDuration = _e === void 0 ? 1000 : _e, _f = _a.style, style = _f === void 0 ? {} : _f, _g = _a.textStyle, textStyle = _g === void 0 ? {} : _g, _h = _a.enabled, enabled = _h === void 0 ? true : _h, _j = _a.direction, direction = _j === void 0 ? 'vertical' : _j;
22
22
  // References for measurement
23
23
  var containerRef = useRef(null);
24
24
  var contentRef = useRef(null);
@@ -88,7 +88,7 @@ var AutoScroll = function (_a) {
88
88
  console.log('Animation setup:', { distance: distance, duration: duration });
89
89
  // Reset position
90
90
  scrollPosition.value = 0;
91
- if (mode === AnimationMode.LOOP) {
91
+ if (mode === AnimationMode.LOOP || mode === 'loop') {
92
92
  // Continuous loop animation
93
93
  scrollPosition.value = withDelay(delay, withRepeat(withTiming(-distance, { duration: duration, easing: Easing.linear }), -1, false));
94
94
  }
@@ -143,4 +143,3 @@ var styles = StyleSheet.create({
143
143
  flexShrink: 0,
144
144
  },
145
145
  });
146
- export default AutoScroll;
@@ -1,5 +1,5 @@
1
- import * as React from "react";
2
- import { ViewStyle, TextStyle } from "react-native";
1
+ import * as React from 'react';
2
+ import { ViewStyle, TextStyle } from 'react-native';
3
3
  /**
4
4
  * Animation modes for the marquee text
5
5
  */
@@ -10,7 +10,7 @@ declare enum AnimationMode {
10
10
  /**
11
11
  * Direction of the marquee animation
12
12
  */
13
- type MarqueeDirection = "horizontal" | "vertical";
13
+ type MarqueeDirection = 'horizontal' | 'vertical';
14
14
  /**
15
15
  * Props for the MarqueeText component
16
16
  */
@@ -60,8 +60,14 @@ interface MarqueeTextProps {
60
60
  /**
61
61
  * Spacing between cloned elements
62
62
  * @default 20
63
+ * @deprecated Use `gap` instead
63
64
  */
64
65
  spacing?: number;
66
+ /**
67
+ * Gap between repeating text loops
68
+ * @default 20
69
+ */
70
+ gap?: number;
65
71
  /**
66
72
  * Allow user interaction with gesture handling
67
73
  * @default true
@@ -115,6 +121,5 @@ export interface MarqueeTextRef {
115
121
  * modes, and user interaction via gestures.
116
122
  */
117
123
  declare const MarqueeText: React.ForwardRefExoticComponent<MarqueeTextProps & React.RefAttributes<MarqueeTextRef>>;
118
- export { AnimationMode };
124
+ export { MarqueeText, AnimationMode };
119
125
  export type { MarqueeDirection, MarqueeTextProps };
120
- export default MarqueeText;
@@ -1,7 +1,7 @@
1
- import * as React from "react";
2
- import { StyleSheet, View, Text, } from "react-native";
3
- import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
- import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useSharedValue, useFrameCallback, withDecay, } from "react-native-reanimated";
1
+ import * as React from 'react';
2
+ import { StyleSheet, View, Text } from 'react-native';
3
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
4
+ import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming, Easing, cancelAnimation, runOnUI, } from 'react-native-reanimated';
5
5
  /**
6
6
  * Animation modes for the marquee text
7
7
  */
@@ -10,32 +10,7 @@ var AnimationMode;
10
10
  AnimationMode["LOOP"] = "loop";
11
11
  AnimationMode["BOUNCE"] = "bounce";
12
12
  })(AnimationMode || (AnimationMode = {}));
13
- /**
14
- * Component to render a child item in the marquee
15
- */
16
- var AnimatedChild = React.memo(function (_a) {
17
- var index = _a.index, children = _a.children, anim = _a.anim, contentMeasurement = _a.contentMeasurement, spacing = _a.spacing, direction = _a.direction;
18
- var style = useAnimatedStyle(function () {
19
- var _a;
20
- var isVertical = direction === "vertical";
21
- var dimension = isVertical
22
- ? contentMeasurement.value.height
23
- : contentMeasurement.value.width;
24
- if (dimension <= 0)
25
- return {};
26
- var position = (index - 1) * (dimension + spacing);
27
- var translation = -(anim.value % (dimension + spacing));
28
- return _a = {
29
- position: "absolute"
30
- },
31
- _a[isVertical ? "top" : "left"] = position,
32
- _a.transform = isVertical
33
- ? [{ translateY: translation }]
34
- : [{ translateX: translation }],
35
- _a;
36
- }, [index, spacing, contentMeasurement, direction]);
37
- return <Animated.View style={style}>{children}</Animated.View>;
38
- });
13
+ // AnimatedChild removed in favor of simplified Flexbox layout
39
14
  /**
40
15
  * MarqueeText component for scrolling text or content
41
16
  *
@@ -44,177 +19,168 @@ var AnimatedChild = React.memo(function (_a) {
44
19
  * modes, and user interaction via gestures.
45
20
  */
46
21
  var MarqueeText = React.forwardRef(function (_a, ref) {
47
- var children = _a.children, _b = _a.mode, mode = _b === void 0 ? AnimationMode.LOOP : _b, _c = _a.speed, speed = _c === void 0 ? 30 : _c, _d = _a.delay, delay = _d === void 0 ? 1500 : _d, _e = _a.endPauseDuration, endPauseDuration = _e === void 0 ? 1000 : _e, style = _a.style, textStyle = _a.textStyle, _f = _a.enabled, enabled = _f === void 0 ? true : _f, _g = _a.direction, direction = _g === void 0 ? "horizontal" : _g, _h = _a.spacing, spacing = _h === void 0 ? 20 : _h, _j = _a.withGesture, withGesture = _j === void 0 ? true : _j, frameRate = _a.frameRate, _k = _a.reverse, reverse = _k === void 0 ? false : _k, _l = _a.backgroundColor, backgroundColor = _l === void 0 ? "transparent" : _l, onAnimationStart = _a.onAnimationStart, onAnimationStop = _a.onAnimationStop;
48
- var _m = React.useState({
49
- width: 0,
50
- height: 0,
51
- }), contentSize = _m[0], setContentSize = _m[1];
52
- var isVertical = direction === "vertical";
22
+ var children = _a.children, _b = _a.mode, mode = _b === void 0 ? AnimationMode.LOOP : _b, _c = _a.speed, speed = _c === void 0 ? 30 : _c, _d = _a.delay, delay = _d === void 0 ? 1500 : _d, _e = _a.endPauseDuration, endPauseDuration = _e === void 0 ? 1000 : _e, style = _a.style, textStyle = _a.textStyle, _f = _a.enabled, enabled = _f === void 0 ? true : _f, _g = _a.direction, direction = _g === void 0 ? 'horizontal' : _g, _h = _a.spacing, spacing = _h === void 0 ? 20 : _h, gap = _a.gap, // New prop
23
+ _j = _a.withGesture, // New prop
24
+ withGesture = _j === void 0 ? true : _j, frameRate = _a.frameRate, _k = _a.reverse, reverse = _k === void 0 ? false : _k, _l = _a.backgroundColor, backgroundColor = _l === void 0 ? 'transparent' : _l, onAnimationStart = _a.onAnimationStart, onAnimationStop = _a.onAnimationStop;
25
+ var isVertical = direction === 'vertical';
53
26
  var isBounceMode = mode === AnimationMode.BOUNCE;
54
- var containerMeasurement = useSharedValue({
55
- width: 0,
56
- height: 0,
57
- x: 0,
58
- y: 0,
59
- });
60
- var contentMeasurement = useSharedValue({
61
- width: 0,
62
- height: 0,
63
- x: 0,
64
- y: 0,
65
- });
66
- var _o = React.useState(0), cloneTimes = _o[0], setCloneTimes = _o[1];
67
- var anim = useSharedValue(0);
68
- var isMounted = React.useRef(true);
69
- var isActive = useSharedValue(false);
70
- var frameRateMs = frameRate ? 1000 / frameRate : 1000 / 60; // Default to 60fps
71
- var pixelsPerMs = speed / 1000;
72
- // Animation frame callback
73
- var frameCallback = useFrameCallback(function (frameInfo) {
74
- if (!enabled || frameInfo.timeSincePreviousFrame === null)
75
- return;
76
- var deltaTime = frameRateMs
77
- ? frameInfo.timeSincePreviousFrame / frameRateMs
78
- : frameInfo.timeSincePreviousFrame;
79
- anim.value += (reverse ? -1 : 1) * pixelsPerMs * deltaTime;
80
- }, false);
81
- // Calculate how many clones we need to fill the screen
27
+ // Resolve gap/spacing (prefer gap)
28
+ var effectiveSpacing = gap !== undefined ? gap : spacing;
29
+ // Shared values
30
+ var containerSize = useSharedValue(0);
31
+ var contentSize = useSharedValue(0);
32
+ var offset = useSharedValue(0);
33
+ var isAnimating = useSharedValue(false);
34
+ // State for cloning
35
+ var _m = React.useState(0), cloneTimes = _m[0], setCloneTimes = _m[1];
36
+ // Calculate how many clones we need
82
37
  useAnimatedReaction(function () {
83
- var contentDim = isVertical
84
- ? contentMeasurement.value.height
85
- : contentMeasurement.value.width;
86
- var containerDim = isVertical
87
- ? containerMeasurement.value.height
88
- : containerMeasurement.value.width;
89
- if (contentDim <= 0 || containerDim <= 0)
38
+ if (contentSize.value === 0 || containerSize.value === 0)
39
+ return 0;
40
+ // Conditional Animation Check:
41
+ // Only loop if content exceeds container?
42
+ // If contentSize <= containerSize, we need 0 clones (just original).
43
+ if (contentSize.value <= containerSize.value) {
90
44
  return 0;
91
- // Need enough clones to fill the container plus buffer for continuous scrolling
92
- return Math.ceil(containerDim / contentDim) + 2;
93
- }, function (times) {
94
- if (times > 0 && isMounted.current) {
95
- runOnJS(setCloneTimes)(times);
96
45
  }
97
- }, [direction]);
98
- // Control functions
99
- var start = React.useCallback(function () {
100
- if (!enabled)
46
+ return Math.ceil(containerSize.value / contentSize.value) + 2;
47
+ }, function (result) {
48
+ if (result !== cloneTimes) {
49
+ runOnJS(setCloneTimes)(result);
50
+ }
51
+ }, [contentSize, containerSize]);
52
+ // Animation Logic
53
+ var startAnimation = React.useCallback(function () {
54
+ 'worklet';
55
+ if (!enabled || contentSize.value === 0 || containerSize.value === 0)
101
56
  return;
102
- frameCallback.setActive(true);
103
- isActive.value = true;
57
+ // If no clones needed (fits in screen), don't animate
58
+ if (contentSize.value <= containerSize.value) {
59
+ cancelAnimation(offset);
60
+ offset.value = 0; // Reset position
61
+ isAnimating.value = false;
62
+ return;
63
+ }
64
+ cancelAnimation(offset);
65
+ isAnimating.value = true;
66
+ var size = contentSize.value + effectiveSpacing;
67
+ if (mode === AnimationMode.LOOP) {
68
+ // Continuous Loop
69
+ var duration = (size / speed) * 1000;
70
+ // Initial delay handled by a separate sequence wrapper
71
+ // The repeating part must be seamless: 0 -> -size, then instant reset to 0
72
+ offset.value = withDelay(delay, withRepeat(withSequence(withTiming(-size, {
73
+ duration: duration,
74
+ easing: Easing.linear,
75
+ }), withTiming(0, { duration: 0 })), -1, // Infinite repeat
76
+ false // No reverse
77
+ ));
78
+ }
79
+ else {
80
+ // Bounce Mode
81
+ var distance = Math.abs(contentSize.value - containerSize.value);
82
+ if (distance <= 0)
83
+ return;
84
+ var target = contentSize.value > containerSize.value
85
+ ? -(contentSize.value - containerSize.value)
86
+ : (containerSize.value - contentSize.value);
87
+ var duration = (Math.abs(target) / speed) * 1000;
88
+ offset.value = withRepeat(withSequence(withDelay(delay, withTiming(target, { duration: duration, easing: Easing.linear })), withDelay(endPauseDuration, withTiming(0, { duration: duration, easing: Easing.linear }))), -1, false);
89
+ }
90
+ }, [enabled, mode, speed, delay, endPauseDuration, effectiveSpacing, containerSize, contentSize]);
91
+ // Start/Stop Controls
92
+ var start = React.useCallback(function () {
93
+ runOnUI(startAnimation)();
104
94
  onAnimationStart === null || onAnimationStart === void 0 ? void 0 : onAnimationStart();
105
- }, [frameCallback, enabled, onAnimationStart]);
95
+ }, [startAnimation, onAnimationStart]);
106
96
  var stop = React.useCallback(function () {
107
- frameCallback.setActive(false);
108
- isActive.value = false;
97
+ cancelAnimation(offset);
98
+ isAnimating.value = false;
109
99
  onAnimationStop === null || onAnimationStop === void 0 ? void 0 : onAnimationStop();
110
- }, [frameCallback, onAnimationStop]);
111
- // Expose controls via ref
100
+ }, [offset, onAnimationStop]);
112
101
  React.useImperativeHandle(ref, function () { return ({
113
102
  start: start,
114
103
  stop: stop,
115
- get isActive() {
116
- return isActive.value;
117
- },
104
+ get isActive() { return isAnimating.value; }
118
105
  }); });
119
- // Setup gesture handling
120
- var pan = React.useMemo(function () {
121
- return Gesture.Pan()
122
- .enabled(withGesture && enabled)
123
- .onBegin(function () {
124
- // Stop auto-animation when user interacts
125
- runOnJS(stop)();
126
- })
127
- .onChange(function (e) {
128
- // Move according to user's gesture
129
- anim.value += -(isVertical ? e.changeY : e.changeX);
130
- })
131
- .onFinalize(function (e) {
132
- // Apply momentum scrolling when user releases
133
- anim.value = withDecay({
134
- velocity: -(isVertical ? e.velocityY : e.velocityX),
135
- }, function (finished) {
136
- // Restart auto-animation when decay finishes
137
- if (finished)
138
- runOnJS(start)();
139
- });
140
- });
141
- }, [withGesture, isVertical, anim, start, stop, enabled]);
142
- // Cleanup on unmount
143
- React.useEffect(function () {
144
- return function () {
145
- isMounted.current = false;
146
- stop();
147
- };
148
- }, [stop]);
149
- // Start/stop based on enabled prop changes
150
- React.useEffect(function () {
151
- if (enabled) {
152
- var timer_1 = setTimeout(function () {
153
- start();
154
- }, delay);
155
- return function () { return clearTimeout(timer_1); };
156
- }
157
- else {
158
- stop();
106
+ // Effect to auto-start when layout is ready
107
+ useAnimatedReaction(function () { return contentSize.value > 0 && containerSize.value > 0 && cloneTimes > 0; }, function (isReady) {
108
+ if (isReady && enabled) {
109
+ runOnJS(start)();
159
110
  }
160
- }, [enabled, start, stop, delay]);
161
- // Render the content
111
+ }, [cloneTimes, enabled, start]);
112
+ // Gesture Handler
113
+ var pan = Gesture.Pan()
114
+ .enabled(withGesture && enabled)
115
+ .onBegin(function () {
116
+ cancelAnimation(offset);
117
+ isAnimating.value = false;
118
+ })
119
+ .onChange(function (e) {
120
+ offset.value += isVertical ? e.changeY : e.changeX;
121
+ })
122
+ .onFinalize(function () {
123
+ // Logic to resume?
124
+ // For simplicity in declaring "clean reset", we might just restart the loop
125
+ // OR we can calculate where we are and finish the current cycle.
126
+ // Restarting is safest to prevent being "out of phase".
127
+ runOnJS(start)();
128
+ });
129
+ var animatedStyle = useAnimatedStyle(function () {
130
+ return {
131
+ transform: [
132
+ isVertical
133
+ ? { translateY: offset.value }
134
+ : { translateX: offset.value }
135
+ ],
136
+ };
137
+ });
162
138
  var renderContent = function () {
163
- if (typeof children === "string") {
139
+ if (typeof children === 'string') {
164
140
  return <Text style={textStyle}>{children}</Text>;
165
141
  }
166
142
  return children;
167
143
  };
168
- return (<Animated.View style={[
169
- styles.container,
170
- { backgroundColor: backgroundColor },
171
- style,
172
- {
173
- height: isVertical && contentMeasurement.value.height > 0
174
- ? contentMeasurement.value.height
175
- : undefined,
176
- width: !isVertical && contentMeasurement.value.width > 0
177
- ? contentMeasurement.value.width
178
- : undefined,
179
- },
180
- ]} onLayout={function (ev) {
181
- containerMeasurement.value = ev.nativeEvent.layout;
182
- }} pointerEvents="box-none">
183
- <GestureDetector gesture={pan}>
184
- <Animated.View style={isVertical ? styles.column : styles.row} pointerEvents="box-none">
185
- {/* Hidden element for measuring original content size */}
186
- <View style={styles.hidden} onLayout={function (ev) {
187
- contentMeasurement.value = ev.nativeEvent.layout;
144
+ return (<View style={[styles.container, { backgroundColor: backgroundColor }, style]} onLayout={function (ev) {
145
+ containerSize.value = isVertical ? ev.nativeEvent.layout.height : ev.nativeEvent.layout.width;
188
146
  }}>
189
- {renderContent()}
190
- </View>
191
-
192
- {/* Visible cloned elements */}
193
- {cloneTimes > 0 &&
194
- Array.from({ length: cloneTimes }).map(function (_, index) { return (<AnimatedChild key={"clone-".concat(index)} index={index} anim={anim} contentMeasurement={contentMeasurement} spacing={spacing} direction={direction}>
147
+ <GestureDetector gesture={pan}>
148
+ <Animated.View style={[isVertical ? styles.column : styles.row, animatedStyle]}>
149
+ {/* Original Items + Clones */}
150
+ {/* We render a list of items based on cloneTimes */}
151
+ {Array.from({ length: Math.max(1, cloneTimes) }).map(function (_, index) {
152
+ var _a;
153
+ // We need to space them out.
154
+ // In this simplified declarative model, we just render them in a flex row/col with gaps/margins.
155
+ // We don't need absolute positioning if we use Flexbox layout properly!
156
+ return (<View key={index} style={_a = {},
157
+ _a[isVertical ? 'marginBottom' : 'marginRight'] = effectiveSpacing,
158
+ _a} onLayout={index === 0 ? function (ev) {
159
+ contentSize.value = isVertical ? ev.nativeEvent.layout.height : ev.nativeEvent.layout.width;
160
+ } : undefined}>
195
161
  {renderContent()}
196
- </AnimatedChild>); })}
162
+ </View>);
163
+ })}
197
164
  </Animated.View>
198
165
  </GestureDetector>
199
- </Animated.View>);
166
+ </View>);
200
167
  });
201
168
  var styles = StyleSheet.create({
202
169
  container: {
203
- overflow: "hidden",
170
+ overflow: 'hidden',
204
171
  },
205
172
  hidden: {
206
173
  opacity: 0,
207
- position: "absolute",
174
+ position: 'absolute',
208
175
  zIndex: -1,
209
176
  },
210
177
  row: {
211
- flexDirection: "row",
212
- position: "relative",
178
+ flexDirection: 'row',
179
+ position: 'relative',
213
180
  },
214
181
  column: {
215
- flexDirection: "column",
216
- position: "relative",
182
+ flexDirection: 'column',
183
+ position: 'relative',
217
184
  },
218
185
  });
219
- export { AnimationMode };
220
- export default MarqueeText;
186
+ export { MarqueeText, AnimationMode };
@@ -1,109 +1,4 @@
1
- import { ViewStyle, TextStyle } from "react-native";
2
- /**
3
- * Animation modes for the marquee text
4
- */
5
- export declare enum AnimationMode {
6
- LOOP = "loop",
7
- BOUNCE = "bounce"
8
- }
9
- /**
10
- * Direction of the marquee animation
11
- */
12
- type MarqueeDirection = 'horizontal' | 'vertical';
13
- /**
14
- * Props for the MarqueeText component
15
- */
16
- export interface MarqueeTextProps {
17
- /**
18
- * Content to be scrolled (string or React nodes)
19
- */
20
- children: React.ReactNode;
21
- /**
22
- * The animation mode: 'loop' for continuous scrolling or 'bounce' for back-and-forth
23
- * @default AnimationMode.LOOP
24
- */
25
- mode?: AnimationMode;
26
- /**
27
- * Speed of the scrolling animation in pixels per second
28
- * @default 30
29
- */
30
- speed?: number;
31
- /**
32
- * Delay in milliseconds before starting the animation
33
- * @default 1500
34
- */
35
- delay?: number;
36
- /**
37
- * Duration in milliseconds to pause at the end of the content (only for bounce mode)
38
- * @default 1000
39
- */
40
- endPauseDuration?: number;
41
- /**
42
- * Container style
43
- */
44
- style?: ViewStyle;
45
- /**
46
- * Text style (when children is a string)
47
- */
48
- textStyle?: TextStyle;
49
- /**
50
- * Whether to enable the animation
51
- * @default true
52
- */
53
- enabled?: boolean;
54
- /**
55
- * Direction of scrolling ('horizontal' or 'vertical')
56
- * @default 'horizontal'
57
- */
58
- direction?: MarqueeDirection;
59
- /**
60
- * Spacing between cloned elements
61
- * @default 20
62
- */
63
- spacing?: number;
64
- /**
65
- * Allow user interaction with gesture handling
66
- * @default true
67
- */
68
- withGesture?: boolean;
69
- /**
70
- * Custom frame rate for animation
71
- */
72
- frameRate?: number;
73
- /**
74
- * Reverse the animation direction
75
- * @default false
76
- */
77
- reverse?: boolean;
78
- /**
79
- * Background color for the container
80
- * @default 'transparent'
81
- */
82
- backgroundColor?: string;
83
- /**
84
- * Callback when animation starts
85
- */
86
- onAnimationStart?: () => void;
87
- /**
88
- * Callback when animation stops
89
- */
90
- onAnimationStop?: () => void;
91
- }
92
- /**
93
- * Reference handle for the MarqueeText component
94
- */
95
- export interface MarqueeTextRef {
96
- /**
97
- * Start the marquee animation
98
- */
99
- start: () => void;
100
- /**
101
- * Stop the marquee animation
102
- */
103
- stop: () => void;
104
- /**
105
- * Whether the animation is currently active
106
- */
107
- isActive: boolean;
108
- }
109
- export {};
1
+ export declare const AnimationMode: {
2
+ LOOP: string;
3
+ BOUNCE: string;
4
+ };
package/dist/constants.js CHANGED
@@ -1,12 +1,4 @@
1
- // export const AnimationMode = {
2
- // LOOP: 'loop',
3
- // BOUNCE: 'bounce',
4
- // };
5
- /**
6
- * Animation modes for the marquee text
7
- */
8
- export var AnimationMode;
9
- (function (AnimationMode) {
10
- AnimationMode["LOOP"] = "loop";
11
- AnimationMode["BOUNCE"] = "bounce";
12
- })(AnimationMode || (AnimationMode = {}));
1
+ export var AnimationMode = {
2
+ LOOP: 'loop',
3
+ BOUNCE: 'bounce',
4
+ };
package/dist/index.d.ts CHANGED
@@ -1,18 +1,6 @@
1
- import { AnimationMode } from './MarqueeText';
2
- export { AnimationMode } from './constants';
3
- declare const Marquee: {
4
- AutoScroll: ({ children, mode, speed, delay, endPauseDuration, style, textStyle, enabled, direction, }: {
5
- children: React.ReactNode;
6
- mode?: import("./constants").AnimationMode;
7
- speed?: number;
8
- delay?: number;
9
- endPauseDuration?: number;
10
- style?: import("react-native").ViewStyle;
11
- textStyle?: import("react-native").TextStyle;
12
- enabled?: boolean;
13
- direction?: "horizontal" | "vertical";
14
- }) => import("react").JSX.Element;
15
- MarqueeText: import("react").ForwardRefExoticComponent<import("./MarqueeText").MarqueeTextProps & import("react").RefAttributes<import("./MarqueeText").MarqueeTextRef>>;
16
- AnimationMode: typeof AnimationMode;
17
- };
18
- export default Marquee;
1
+ import { AutoScroll } from './AutoScroll';
2
+ import { AnimationMode } from './constants';
3
+ declare const MarqueeTextShim: any;
4
+ declare const MarqueeShim: any;
5
+ export { AutoScroll, AnimationMode, MarqueeTextShim as MarqueeText, MarqueeShim as Marquee };
6
+ export default AutoScroll;
package/dist/index.js CHANGED
@@ -1,10 +1,8 @@
1
- import AutoScroll from './AutoScroll';
2
- import MarqueeText, { AnimationMode } from './MarqueeText';
3
- export { AnimationMode } from './constants';
4
- // Optional: Default export (choose either AutoScroll or MarqueeText as default)
5
- var Marquee = {
6
- AutoScroll: AutoScroll,
7
- MarqueeText: MarqueeText,
8
- AnimationMode: AnimationMode,
9
- };
10
- export default Marquee;
1
+ import { AutoScroll } from './AutoScroll';
2
+ import { MarqueeText } from './MarqueeText';
3
+ import { AnimationMode } from './constants';
4
+ // @ts-ignore - Shim for React 18/19 compatibility
5
+ var MarqueeTextShim = MarqueeText;
6
+ var MarqueeShim = MarqueeText;
7
+ export { AutoScroll, AnimationMode, MarqueeTextShim as MarqueeText, MarqueeShim as Marquee };
8
+ export default AutoScroll;
package/package.json CHANGED
@@ -1,60 +1,58 @@
1
1
  {
2
- "name": "rn-marquee-text",
3
- "version": "2.1.1-beta.1",
4
- "description": "A customizable marquee (scrolling) text component for React Native",
5
- "main": "index.js",
6
- "types": "index.d.ts",
7
- "scripts": {
8
- "test": "echo \"Error: no test specified\" && exit 1",
9
- "build": "tsc",
10
- "prepare": "npm run build"
11
- },
12
- "publishConfig": {
13
- "tag": "beta"
14
- },
15
- "files": [
16
- "dist",
17
- "index.js",
18
- "README.md",
19
- "index.d.ts"
20
- ],
21
- "keywords": [
22
- "react-native",
23
- "marquee",
24
- "scrolling-text",
25
- "text",
26
- "animation",
27
- "react-native-component",
28
- "react",
29
- "native",
30
- "expo"
31
- ],
32
- "author": "Paresh Chavda",
33
- "license": "MIT",
34
- "repository": {
35
- "type": "git",
36
- "url": "git+https://github.com/pareshchavda/rn-marquee-text-public.git"
37
- },
38
- "bugs": {
39
- "url": "https://github.com/pareshchavda/rn-marquee-text-public/issues"
40
- },
41
- "homepage": "https://github.com/pareshchavda/rn-marquee-text-public#readme",
42
- "peerDependencies": {
43
- "prop-types": ">=15.0.0",
44
- "react": "*",
45
- "react-native": "*",
46
- "react-native-reanimated": ">=2.0.0"
47
- },
48
- "devDependencies": {
49
- "@types/react": "^19.1.2",
50
- "prop-types": "^15.8.1",
51
- "react": "^18.2.0",
52
- "react-native": "^0.72.17",
53
- "react-native-reanimated": "^3.17.5",
54
- "typescript": "^5.0.0"
55
- },
56
- "dependencies": {
57
- "@react-navigation/native": "^7.1.9",
58
- "react-native-gesture-handler": "^2.25.0"
59
- }
60
- }
2
+ "name": "rn-marquee-text",
3
+ "version": "3.0.0",
4
+ "description": "A customizable marquee (scrolling) text component for React Native",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1",
9
+ "build": "tsc",
10
+ "prepare": "npm run build"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "index.js",
15
+ "README.md"
16
+ ],
17
+ "keywords": [
18
+ "react-native",
19
+ "marquee",
20
+ "scrolling-text",
21
+ "text",
22
+ "animation",
23
+ "react-native-component",
24
+ "react",
25
+ "native",
26
+ "expo",
27
+ "Marquee"
28
+ ],
29
+ "author": "Paresh Chavda",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/pareshchavda/rn-marquee-text-public.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/pareshchavda/rn-marquee-text-public/issues"
37
+ },
38
+ "homepage": "https://github.com/pareshchavda/rn-marquee-text-public#readme",
39
+ "peerDependencies": {
40
+ "prop-types": ">=15.0.0",
41
+ "react": "*",
42
+ "react-native": "*",
43
+ "react-native-reanimated": ">=2.0.0",
44
+ "@react-navigation/native": ">=6.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "@react-navigation/native": "^7.1.9",
48
+ "@types/react": "^19.1.2",
49
+ "prop-types": "^15.8.1",
50
+ "react": "^18.2.0",
51
+ "react-native": "^0.72.17",
52
+ "react-native-reanimated": "^3.17.5",
53
+ "typescript": "^5.0.0"
54
+ },
55
+ "dependencies": {
56
+ "react-native-gesture-handler": "^2.25.0"
57
+ }
58
+ }