gsap-react-marquee 0.3.1 → 0.4.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 +375 -91
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +52 -8
- package/dist/index.esm.js +1 -1
- package/package.json +52 -21
- package/dist/gsap-react-marquee.css +0 -1
- package/dist/index.cjs.js +0 -1
- package/dist/types/components/gsap-react-marquee.d.ts +0 -4
- package/dist/types/components/gsap-react-marquee.styles.d.ts +0 -2
- package/dist/types/components/gsap-react-marquee.type.d.ts +0 -18
- package/dist/types/components/gsap-reactmarquee.utils.d.ts +0 -10
- package/dist/types/index.d.ts +0 -3
package/README.md
CHANGED
|
@@ -1,150 +1,434 @@
|
|
|
1
1
|
# GSAP React Marquee
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`gsap-react-marquee` is a React marquee component powered by GSAP. It supports horizontal and vertical scrolling, seamless looping, optional fill mode, pause-on-hover, scroll-follow speed changes, draggable interaction, reduced-motion preferences, accessible visual clones, gradient overlays, and TypeScript types.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install gsap-react-marquee
|
|
9
|
-
# or
|
|
10
|
-
yarn add gsap-react-marquee
|
|
11
|
-
# or
|
|
12
|
-
pnpm add gsap-react-marquee
|
|
8
|
+
npm install gsap-react-marquee gsap @gsap/react
|
|
13
9
|
```
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
```bash
|
|
12
|
+
yarn add gsap-react-marquee gsap @gsap/react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add gsap-react-marquee gsap @gsap/react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`react`, `gsap`, and `@gsap/react` are peer dependencies and must be installed by the consuming app. Your React application may still use `react-dom`, but this package does not require it directly.
|
|
20
|
+
|
|
21
|
+
### Compatibility
|
|
22
|
+
|
|
23
|
+
| Environment | Supported versions | Release verification |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| React | 18 and 19 | Packed ESM, CommonJS, types, SSR, CSS, and Chromium fixtures |
|
|
26
|
+
| GSAP | 3.12 and 3.13 | Packed fixtures use 3.12.5 and 3.13.0 |
|
|
27
|
+
| `@gsap/react` | 2.1 or newer | Packed fixtures use 2.1.2 |
|
|
28
|
+
| Node.js | 20 and 22 | Typecheck, lint, unit, build, package, and audit release matrix |
|
|
29
|
+
|
|
30
|
+
Node.js versions older than 20 are not part of the `0.4.0` support contract.
|
|
31
|
+
|
|
32
|
+
## Basic Usage
|
|
16
33
|
|
|
17
34
|
```tsx
|
|
18
35
|
import Marquee from "gsap-react-marquee";
|
|
19
36
|
|
|
20
|
-
function App() {
|
|
37
|
+
export function App() {
|
|
21
38
|
return (
|
|
22
|
-
<Marquee dir="left" speed={100}
|
|
23
|
-
<
|
|
39
|
+
<Marquee dir="left" speed={100} spacing={16}>
|
|
40
|
+
<span>Scrolling content</span>
|
|
24
41
|
</Marquee>
|
|
25
42
|
);
|
|
26
43
|
}
|
|
27
44
|
```
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
The package injects its base CSS through the bundled entrypoint, so no separate stylesheet import is required.
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
- **Seamless Looping**: Advanced duplicate calculation ensures smooth infinite scrolling without gaps
|
|
33
|
-
- **High Performance**: Built with GSAP for optimal animation performance
|
|
34
|
-
- **Responsive Design**: Adapts to different screen sizes and container dimensions
|
|
35
|
-
- **Multiple Directions**: Support for horizontal (left/right) and vertical (up/down) scrolling with proper axis handling
|
|
36
|
-
- **Smart Gradient Overlays**: Automatic gradient positioning based on marquee orientation
|
|
37
|
-
- **Interactive Controls**: Optional draggable interface and scroll synchronization
|
|
38
|
-
- **TypeScript Support**: Full type safety and IntelliSense support
|
|
48
|
+
## Examples
|
|
39
49
|
|
|
40
|
-
|
|
50
|
+
### Continuous Fill
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
| --------------- | ------------------------------------- | -------- | ------------------------------------------------------------- |
|
|
44
|
-
| `children` | `ReactNode` | – | Content to render inside the marquee |
|
|
45
|
-
| `className` | `string` | – | Additional CSS classes for styling |
|
|
46
|
-
| `dir` | `"right" \| "left" \| "up" \| "down"` | `"left"` | Direction of the marquee movement |
|
|
47
|
-
| `loop` | `number` | `-1` | Number of loops (`-1` = infinite) |
|
|
48
|
-
| `paused` | `boolean` | `false` | Whether the marquee animation should be paused |
|
|
49
|
-
| `delay` | `number` | `0` | Delay before the animation starts (in seconds) |
|
|
50
|
-
| `speed` | `number` | `100` | Speed of the marquee animation in px/s |
|
|
51
|
-
| `fill` | `boolean` | `false` | Whether the marquee should continuously fill the space |
|
|
52
|
-
| `pauseOnHover` | `boolean` | `false` | Pause the marquee when hovering |
|
|
53
|
-
| `gradient` | `boolean` | `false` | Enable gradient overlay (auto-adapts to orientation) |
|
|
54
|
-
| `gradientColor` | `string` | – | Color of the gradient if enabled |
|
|
55
|
-
| `spacing` | `number` | `16` | Spacing between repeated elements in px |
|
|
56
|
-
| `draggable` | `boolean` | `false` | Enable dragging to scroll manually |
|
|
57
|
-
| `scrollFollow` | `boolean` | `false` | Sync marquee with page scroll direction |
|
|
58
|
-
| `scrollSpeed` | `number` | `2.5` | Speed factor when syncing with page scroll (max: 4, min: 1.1) |
|
|
52
|
+
Use `fill` when a short piece of content should repeat enough times to cover the visible marquee area.
|
|
59
53
|
|
|
60
|
-
|
|
54
|
+
```tsx
|
|
55
|
+
<Marquee fill spacing={24} speed={80}>
|
|
56
|
+
<span>React</span>
|
|
57
|
+
<span>GSAP</span>
|
|
58
|
+
<span>Animation</span>
|
|
59
|
+
</Marquee>
|
|
60
|
+
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
`fill` is independent from `dir`. It uses the same coverage calculation for
|
|
63
|
+
left, right, up, and down movement. Without `fill`, every direction renders one
|
|
64
|
+
semantic original and one visual clone.
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
### Vertical Marquee
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
```tsx
|
|
69
|
+
<Marquee
|
|
70
|
+
dir="up"
|
|
71
|
+
fill
|
|
72
|
+
speed={80}
|
|
73
|
+
spacing={12}
|
|
74
|
+
containerStyle={{ height: 320 }}
|
|
75
|
+
>
|
|
76
|
+
<div>Item 1</div>
|
|
77
|
+
<div>Item 2</div>
|
|
78
|
+
<div>Item 3</div>
|
|
79
|
+
</Marquee>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use `containerStyle` or `containerClassName` when the surrounding layout does
|
|
83
|
+
not already provide a constrained height.
|
|
70
84
|
|
|
71
|
-
###
|
|
85
|
+
### Gradient Overlay
|
|
72
86
|
|
|
73
|
-
|
|
87
|
+
When `gradient` is enabled, the component detects the nearest non-transparent background color and uses it for the edge fade. You can override the color with `gradientColor`.
|
|
74
88
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
```tsx
|
|
90
|
+
<Marquee gradient gradientColor="#ffffff">
|
|
91
|
+
<span>Faded edges</span>
|
|
92
|
+
</Marquee>
|
|
93
|
+
```
|
|
78
94
|
|
|
79
|
-
###
|
|
95
|
+
### Pause On Hover
|
|
80
96
|
|
|
81
|
-
|
|
97
|
+
```tsx
|
|
98
|
+
<Marquee pauseOnHover>
|
|
99
|
+
<span>Hover to pause</span>
|
|
100
|
+
</Marquee>
|
|
101
|
+
```
|
|
82
102
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
- **Smart Gradients**: Gradient overlays automatically position themselves based on scroll direction
|
|
86
|
-
- Horizontal: Side gradients (left and right edges)
|
|
87
|
-
- Vertical: Top and bottom gradients
|
|
103
|
+
`pauseOnHover` also pauses while keyboard focus is inside the marquee. Leaving
|
|
104
|
+
with either pointer or focus cannot override a controlled `paused={true}`.
|
|
88
105
|
|
|
89
|
-
###
|
|
106
|
+
### Controlled Pause
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
`paused` is controlled React state, not only an initial setting. Changing it to
|
|
109
|
+
`true` pauses the current direction; changing it to `false` resumes that
|
|
110
|
+
direction at the base speed. Hover/focus leave, drag release, scroll response,
|
|
111
|
+
reverse delay, and late plugin loading cannot resume `paused={true}`. Draggable
|
|
112
|
+
still initializes while controlled-paused so it becomes usable when resumed.
|
|
95
113
|
|
|
96
|
-
|
|
114
|
+
Applications showing motion for more than five seconds should expose a visible
|
|
115
|
+
pause control when their accessibility requirements call for one. The component
|
|
116
|
+
provides controlled state; the consumer owns control placement and labeling:
|
|
97
117
|
|
|
98
|
-
|
|
118
|
+
```tsx
|
|
119
|
+
import { useState } from "react";
|
|
120
|
+
import Marquee from "gsap-react-marquee";
|
|
121
|
+
|
|
122
|
+
export function ControlledMarquee() {
|
|
123
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<section aria-label="Partner announcements">
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
aria-pressed={isPaused}
|
|
130
|
+
onClick={() => setIsPaused((current) => !current)}
|
|
131
|
+
>
|
|
132
|
+
{isPaused ? "Play marquee" : "Pause marquee"}
|
|
133
|
+
</button>
|
|
134
|
+
<Marquee paused={isPaused} draggable>
|
|
135
|
+
<span>Controlled animation</span>
|
|
136
|
+
</Marquee>
|
|
137
|
+
</section>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`respectReducedMotion` handles user motion preference. It does not remove the
|
|
143
|
+
consumer's responsibility to provide pause/stop controls required by content,
|
|
144
|
+
duration, and applicable WCAG policy.
|
|
145
|
+
|
|
146
|
+
### Scroll-Follow
|
|
147
|
+
|
|
148
|
+
`scrollFollow` changes the marquee timeline speed and direction based on vertical wheel movement.
|
|
99
149
|
|
|
100
150
|
```tsx
|
|
101
|
-
<Marquee
|
|
102
|
-
<span>
|
|
151
|
+
<Marquee scrollFollow scrollSpeed={3}>
|
|
152
|
+
<span>Scroll the page or wheel over the document</span>
|
|
103
153
|
</Marquee>
|
|
104
154
|
```
|
|
105
155
|
|
|
106
|
-
###
|
|
156
|
+
### Draggable
|
|
157
|
+
|
|
158
|
+
`draggable` lets users drag the marquee track manually. The package loads `Draggable` only when this prop is enabled. Momentum throwing is enabled when your GSAP setup has already registered `InertiaPlugin`; otherwise direct dragging still works without the inertia throw. This feature detection keeps the package compatible with GSAP 3.12 distributions, which do not all expose `InertiaPlugin` at the same package path.
|
|
107
159
|
|
|
108
160
|
```tsx
|
|
109
|
-
<Marquee
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
<
|
|
161
|
+
<Marquee draggable pauseOnHover>
|
|
162
|
+
<img src="/image-1.jpg" alt="Gallery image 1" />
|
|
163
|
+
<img src="/image-2.jpg" alt="Gallery image 2" />
|
|
164
|
+
<img src="/image-3.jpg" alt="Gallery image 3" />
|
|
165
|
+
</Marquee>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Forwarded Ref
|
|
169
|
+
|
|
170
|
+
The component forwards a ref to the root marquee container. Both object refs and callback refs are supported.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import { useRef } from "react";
|
|
174
|
+
import Marquee from "gsap-react-marquee";
|
|
175
|
+
|
|
176
|
+
export function Example() {
|
|
177
|
+
const ref = useRef<HTMLDivElement | null>(null);
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<Marquee ref={ref} fill>
|
|
181
|
+
<span>Measured container</span>
|
|
182
|
+
</Marquee>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Reduced Motion
|
|
188
|
+
|
|
189
|
+
The component respects `prefers-reduced-motion: reduce` by default. It renders
|
|
190
|
+
one static original and creates no GSAP timeline, Observer, or Draggable
|
|
191
|
+
instance. Preference changes are applied without reloading the page.
|
|
192
|
+
|
|
193
|
+
Use `respectReducedMotion={false}` only when motion remains appropriate for the
|
|
194
|
+
content and audience:
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
<Marquee respectReducedMotion={false}>
|
|
198
|
+
<span>Animation remains enabled</span>
|
|
113
199
|
</Marquee>
|
|
114
200
|
```
|
|
115
201
|
|
|
116
|
-
###
|
|
202
|
+
### Accessibility and Child Content
|
|
203
|
+
|
|
204
|
+
Version `0.4.0` supports presentational children such as text, images, and logo
|
|
205
|
+
groups. Interactive controls, links, stable IDs, and ID-reference relationships
|
|
206
|
+
inside repeated children are not supported. A future major version may add an
|
|
207
|
+
explicit render-item API for interactive content.
|
|
208
|
+
|
|
209
|
+
SSR output contains only the semantic original. After client measurement,
|
|
210
|
+
visual clones receive `aria-hidden="true"`, native `inert` where available, and
|
|
211
|
+
disabled pointer interaction. The Safari 14.1 fallback removes clone tab stops,
|
|
212
|
+
form names, IDs, and ID-reference attributes and prevents programmatic focus
|
|
213
|
+
from remaining inside a clone. Development builds warn once when unsupported
|
|
214
|
+
child content is detected.
|
|
215
|
+
|
|
216
|
+
Clone sanitization means ID-based CSS, fragment links, and SVG references inside
|
|
217
|
+
repeated children may not survive. Keep those relationships outside marquee
|
|
218
|
+
children.
|
|
219
|
+
|
|
220
|
+
Use `containerProps` for root semantics and event handlers:
|
|
117
221
|
|
|
118
222
|
```tsx
|
|
119
|
-
<Marquee
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
223
|
+
<Marquee
|
|
224
|
+
containerProps={{
|
|
225
|
+
"aria-label": "Technology partners",
|
|
226
|
+
role: "region",
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<span>Presentational partner logos</span>
|
|
123
230
|
</Marquee>
|
|
124
231
|
```
|
|
125
232
|
|
|
126
|
-
|
|
233
|
+
## Props
|
|
234
|
+
|
|
235
|
+
| Prop | Type | Default | Description |
|
|
236
|
+
| --- | --- | --- | --- |
|
|
237
|
+
| `children` | `ReactNode` | Required | Content rendered inside each marquee item. |
|
|
238
|
+
| `className` | `string` | `undefined` | Class applied to each `.gsap-react-marquee-content` element. |
|
|
239
|
+
| `containerClassName` | `string` | `undefined` | Class applied only to the root viewport. |
|
|
240
|
+
| `containerStyle` | `CSSProperties` | `undefined` | Inline styles applied to the root viewport. |
|
|
241
|
+
| `containerProps` | root `div` attributes | `undefined` | ARIA, `data-*`, and event props applied to the root viewport. |
|
|
242
|
+
| `dir` | `"left" \| "right" \| "up" \| "down"` | `"left"` | Direction of movement. |
|
|
243
|
+
| `loop` | `number` | `-1` | Number of timeline repeats. `-1` means infinite. |
|
|
244
|
+
| `paused` | `boolean` | `false` | Controls whether the current timeline is paused. |
|
|
245
|
+
| `respectReducedMotion` | `boolean` | `true` | Shows one static original when reduced motion is requested. |
|
|
246
|
+
| `delay` | `number` | `0` | Delay in seconds before the timeline starts. |
|
|
247
|
+
| `speed` | `number` | `100` | Animation speed in pixels per second. |
|
|
248
|
+
| `fill` | `boolean` | `false` | Repeats content enough times to cover the measured marquee area. |
|
|
249
|
+
| `maxDuplicates` | `number` | `100` | Maximum additional clones in fill mode, capped internally at `250`. |
|
|
250
|
+
| `pauseOnHover` | `boolean` | `false` | Pauses on pointer hover and resumes on leave. |
|
|
251
|
+
| `gradient` | `boolean` | `false` | Enables edge gradient overlays. |
|
|
252
|
+
| `gradientColor` | `string` | `undefined` | Explicit gradient color. Overrides automatic background detection. |
|
|
253
|
+
| `spacing` | `number` | `16` | Gap between marquee items, in pixels. |
|
|
254
|
+
| `draggable` | `boolean` | `false` | Enables manual drag control. |
|
|
255
|
+
| `scrollFollow` | `boolean` | `false` | Adjusts timeline speed from wheel/scroll direction. |
|
|
256
|
+
| `scrollSpeed` | `number` | `2.5` | Scroll-follow multiplier. Clamped between `1.1` and `4`. |
|
|
257
|
+
|
|
258
|
+
### Numeric Normalization
|
|
259
|
+
|
|
260
|
+
Numeric props are normalized before layout or GSAP receives them. Invalid
|
|
261
|
+
values never create negative, `NaN`, or infinite durations.
|
|
262
|
+
|
|
263
|
+
| Prop | Accepted values | Invalid-value behavior |
|
|
264
|
+
| --- | --- | --- |
|
|
265
|
+
| `speed` | finite number greater than `0` | Uses `100` |
|
|
266
|
+
| `spacing` | finite number at least `0` | Uses `16` |
|
|
267
|
+
| `delay` | finite number at least `0` | Uses `0` |
|
|
268
|
+
| `loop` | `-1` or integer at least `0` | Uses `-1` |
|
|
269
|
+
| `scrollSpeed` | finite number | Clamped to `1.1`–`4`; non-finite uses `2.5` |
|
|
270
|
+
| `maxDuplicates` | positive integer | Uses `100`, then caps at hard ceiling `250` |
|
|
271
|
+
|
|
272
|
+
## How Sizing Works
|
|
273
|
+
|
|
274
|
+
The component renders one semantic original during SSR, then measures the root container and first content item after client mount. It creates enough inaccessible visual clones for the selected mode and starts a GSAP timeline unless reduced motion is active.
|
|
275
|
+
|
|
276
|
+
In normal mode (`fill={false}`), the component renders one original item plus
|
|
277
|
+
one clone. Each repeated wrapper automatically spans at least the active
|
|
278
|
+
viewport axis and grows when its natural content is larger. Short vertical
|
|
279
|
+
content therefore re-enters from the far edge instead of the middle; `fill` is
|
|
280
|
+
not required for correct entry geometry.
|
|
281
|
+
|
|
282
|
+
In fill mode (`fill={true}`), the component calculates how many clones are required to cover the measured target size plus one seamless wrap segment. The calculation includes `spacing` and behaves identically for horizontal and vertical directions. `maxDuplicates` defaults to `100` and has a hard internal ceiling of `250`. When a configured ceiling prevents full coverage, development builds emit one warning and keep the rendered clone count finite.
|
|
283
|
+
|
|
284
|
+
Clone calculation uses the root container's measured width or height. The root automatically fills the parent's width in horizontal mode and the parent's width and height in vertical mode. Page layout defines the available viewport; no width or height prop is required on `Marquee`. Clone-generated size changes are excluded from subsequent viewport measurements.
|
|
285
|
+
|
|
286
|
+
## Styling
|
|
287
|
+
|
|
288
|
+
The root element receives these classes:
|
|
289
|
+
|
|
290
|
+
```html
|
|
291
|
+
<div class="gsap-react-marquee-container">
|
|
292
|
+
<div class="gsap-react-marquee">
|
|
293
|
+
<div class="gsap-react-marquee-content">...</div>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Vertical marquees also receive:
|
|
299
|
+
|
|
300
|
+
```html
|
|
301
|
+
<div class="gsap-react-marquee-container gsap-react-marquee-vertical">
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Use `className` to style the repeated content wrapper:
|
|
127
305
|
|
|
128
306
|
```tsx
|
|
129
|
-
<Marquee
|
|
130
|
-
<
|
|
307
|
+
<Marquee className="items-center gap-4">
|
|
308
|
+
<span>One</span>
|
|
309
|
+
<span>Two</span>
|
|
131
310
|
</Marquee>
|
|
132
311
|
```
|
|
133
312
|
|
|
134
|
-
|
|
313
|
+
Use `containerClassName` or `containerStyle` only when the automatic viewport
|
|
314
|
+
needs an explicit visual or sizing override:
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
<Marquee
|
|
318
|
+
dir="up"
|
|
319
|
+
fill
|
|
320
|
+
containerClassName="vertical-marquee"
|
|
321
|
+
containerStyle={{ height: 320, backgroundColor: "#111827" }}
|
|
322
|
+
>
|
|
323
|
+
<span>Measured vertical content</span>
|
|
324
|
+
</Marquee>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Required root layout styles remain controlled by the component. The root fills
|
|
328
|
+
the available parent viewport automatically. Width, height, color, background,
|
|
329
|
+
and other non-critical styles can still be overridden through `containerStyle`.
|
|
330
|
+
|
|
331
|
+
## Public Utilities
|
|
332
|
+
|
|
333
|
+
Version `0.4.0` keeps the historical utility exports as supported public API.
|
|
334
|
+
Aliases below are identical function references, not wrappers.
|
|
335
|
+
|
|
336
|
+
| Utility | Contract |
|
|
337
|
+
| --- | --- |
|
|
338
|
+
| `normalizeMarqueeOptions` | Returns finite normalized numeric options using documented fallbacks and caps. |
|
|
339
|
+
| `hasUsableMeasurement` | Returns `true` only when every supplied number is finite and greater than zero. |
|
|
340
|
+
| `hasDefinedWidth` | Checks whether `offsetWidth` is finite and positive; it no longer infers CSS width semantics. |
|
|
341
|
+
| `getTargetSize` / `getTargetWidth` | Returns root `offsetHeight` or `offsetWidth`; no viewport fallback is used. |
|
|
342
|
+
| `calculateDuplicateCountResult` | Returns bounded clone count, required count, and limit state. Fill includes spacing and one wrap segment. |
|
|
343
|
+
| `calculateDuplicateCount` / `calculateDuplicates` | Returns only the bounded additional-clone count from the same calculation. |
|
|
344
|
+
| `getMinSize` / `getMinWidth` | Returns active-axis wrapper minimum size for normal or fill layout. |
|
|
345
|
+
| `setupContainerStyles` | Applies direction, spacing, flex sizing, and overflow to root, track, and content elements. |
|
|
346
|
+
| `resumeTimeline` | Restores forward/reverse playback or controlled pause from explicit options. |
|
|
347
|
+
| `createMarqueeAnimation` / `coreAnimation` | Builds segments and optional drag behavior, honoring loop, reverse, pause, and cleanup contracts. |
|
|
348
|
+
| `getEffectiveBackgroundColor` | Returns the first non-transparent computed ancestor background, or `transparent`. |
|
|
349
|
+
| `cn` | Combines `clsx` inputs and resolves Tailwind class conflicts. |
|
|
350
|
+
|
|
351
|
+
These `0.4.0` behavior changes are intentional. Future removal or narrowing of
|
|
352
|
+
historical helpers requires deprecation before a major release.
|
|
353
|
+
|
|
354
|
+
## Runtime Notes
|
|
355
|
+
|
|
356
|
+
- SSR safely renders one static original. Client measurement adds visual clones after hydration.
|
|
357
|
+
- The animated lifecycle uses an isomorphic layout effect, `ResizeObserver`, `requestAnimationFrame`, and DOM measurements.
|
|
358
|
+
- In SSR frameworks such as Next.js, render it from a client component. Add `"use client"` to the file that imports and renders the marquee.
|
|
359
|
+
- Images that are not complete at mount are watched and trigger a re-measure after `load` or `error`.
|
|
360
|
+
- Observer and Draggable load independently. A failed optional chunk disables only that feature; the base marquee still starts. Toggling the feature off and on requests a retry.
|
|
361
|
+
- Changing animation props such as `dir`, `speed`, `delay`, `fill`, `maxDuplicates`, `draggable`, `spacing`, `loop`, or `paused` re-initializes the GSAP timeline.
|
|
362
|
+
- ESM and CommonJS bundles contain the same minified runtime and inject one minified base-style element in browsers. Importing either entrypoint during SSR does not access `document`.
|
|
363
|
+
- Published JavaScript and CSS are minified, while supported development diagnostics remain intact. Source maps are intentionally not published for `0.4.0`; repository TypeScript remains the review/debug source.
|
|
364
|
+
|
|
365
|
+
## Browser Support
|
|
366
|
+
|
|
367
|
+
Supported targets are current and previous-major Chromium and Firefox releases,
|
|
368
|
+
plus Safari and iOS Safari 14.1 or newer. Native `inert` is feature-detected.
|
|
369
|
+
Safari versions without it use the clone-specific focus, form, and pointer
|
|
370
|
+
fallback described above. Playwright Chromium, Firefox, and WebKit are the automated
|
|
371
|
+
compatibility signals; WebKit does not exactly emulate every older Safari
|
|
372
|
+
release.
|
|
373
|
+
|
|
374
|
+
## Troubleshooting
|
|
375
|
+
|
|
376
|
+
### The marquee has gaps
|
|
377
|
+
|
|
378
|
+
Use `fill={true}` for short content, increase `spacing` only as much as needed, and make sure images/fonts have stable dimensions. For image-heavy marquees, set explicit image width and height to reduce layout shifts.
|
|
379
|
+
|
|
380
|
+
### Vertical marquee does not move correctly
|
|
381
|
+
|
|
382
|
+
Ensure the surrounding page layout exposes usable vertical space. The marquee
|
|
383
|
+
automatically fills that parent space and measures its own root; do not copy the
|
|
384
|
+
parent height onto `Marquee`.
|
|
385
|
+
|
|
386
|
+
### The marquee expands the page
|
|
387
|
+
|
|
388
|
+
Constrain the surrounding page region with normal CSS layout. The marquee root
|
|
389
|
+
automatically consumes that region instead of expanding to the cloned track.
|
|
390
|
+
|
|
391
|
+
### Dragging has no momentum
|
|
392
|
+
|
|
393
|
+
Momentum depends on GSAP `InertiaPlugin` availability. If the plugin is not available in your GSAP installation, dragging can still work without inertia-style throwing.
|
|
394
|
+
|
|
395
|
+
## Development
|
|
396
|
+
|
|
397
|
+
Use Node.js 20 or 22 and the pinned pnpm version from `packageManager`.
|
|
398
|
+
|
|
399
|
+
Install the exact lockfile:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
pnpm install --frozen-lockfile
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Run TypeScript, lint, and unit checks:
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
pnpm run check
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Run browser compatibility checks:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
pnpm run test:browser
|
|
415
|
+
pnpm run test:browser:firefox
|
|
416
|
+
pnpm run test:browser:webkit
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Build and test the real packed artifact against React 18 and 19 consumers:
|
|
135
420
|
|
|
136
|
-
|
|
421
|
+
```bash
|
|
422
|
+
pnpm run test:package
|
|
423
|
+
```
|
|
137
424
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
425
|
+
Run the full dependency audit or complete local release gate:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
pnpm run audit
|
|
429
|
+
pnpm run release:check
|
|
430
|
+
```
|
|
143
431
|
|
|
144
|
-
|
|
432
|
+
## License
|
|
145
433
|
|
|
146
|
-
|
|
147
|
-
- 🚀 Enhanced duplicate calculation algorithm for better performance
|
|
148
|
-
- 🔧 Improved seamless looping with smarter target width calculation
|
|
149
|
-
- 📝 Added comprehensive debug logging for development
|
|
150
|
-
- 🛡️ Added safety limits to prevent extreme duplicate scenarios
|
|
434
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),t=require("@gsap/react"),r=require("gsap"),n=require("react"),a=require("clsx"),i=require("tailwind-merge");if("undefined"!=typeof document&&!document.getElementById("gsap-react-marquee-styles")){const e=document.createElement("style");e.id="gsap-react-marquee-styles",e.textContent='.gsap-react-marquee-container{white-space:nowrap;width:100%;display:flex;position:relative;overflow:hidden}:where(.gsap-react-marquee-container.gsap-react-marquee-vertical){align-self:stretch;height:100%;min-height:0}.gsap-react-marquee-container:after{content:"";background:linear-gradient(270deg, #fff0 0%, var(--gradient-color) 75%);z-index:10;pointer-events:none;width:15%;height:100%;position:absolute;top:0;left:0}.gsap-react-marquee-container:before{content:"";background:linear-gradient(90deg, #fff0 0%, var(--gradient-color) 75%);z-index:10;pointer-events:none;width:15%;height:100%;position:absolute;top:0;right:0}.gsap-react-marquee-vertical:after{background:linear-gradient(180deg, #fff0 0%, var(--gradient-color) 75%);width:100%;height:50%;top:60%;left:0}.gsap-react-marquee-vertical:before{background:linear-gradient(360deg, #fff0 0%, var(--gradient-color) 75%);width:100%;height:50%;top:0;left:0;right:auto}.gsap-react-marquee{white-space:nowrap;flex:1;width:auto;height:max-content;line-height:100%;display:flex}.gsap-react-marquee-clone,.gsap-react-marquee-clone *{pointer-events:none}.gsap-react-marquee-content{white-space:nowrap;width:max-content;line-height:100%;display:flex;overflow:hidden}',document.head.appendChild(e)}const l="[data-gsap-react-marquee-clone]",o=["a[href]","area[href]","audio[controls]","button","embed","iframe","input:not([type='hidden'])","object","select","summary","textarea","video[controls]","[contenteditable]:not([contenteditable='false'])","[tabindex]"].join(","),s=["[id]","a[href]","area[href]","button","embed","iframe","input","object","select","textarea","[contenteditable]:not([contenteditable='false'])"].join(","),u=["aria-activedescendant","aria-controls","aria-describedby","aria-details","aria-errormessage","aria-flowto","aria-labelledby","aria-owns","for","form","headers","itemref","list"],c=["clip-path","fill","filter","marker-end","marker-mid","marker-start","mask","stroke"],d=/^\s*#/,p=/url\(\s*["']?#/i,f=()=>"undefined"!=typeof HTMLElement&&"inert"in HTMLElement.prototype,g=e=>{const t=e.matches(o);e.removeAttribute("id"),e.removeAttribute("name"),u.forEach(t=>{e.removeAttribute(t)});const r=e.getAttribute("href");r&&d.test(r)&&e.removeAttribute("href");const n=e.getAttribute("xlink:href");n&&d.test(n)&&e.removeAttribute("xlink:href"),c.forEach(t=>{const r=e.getAttribute(t);r&&p.test(r)&&e.removeAttribute(t)}),e instanceof HTMLElement&&Array.from(e.style).forEach(t=>{p.test(e.style.getPropertyValue(t))&&e.style.removeProperty(t)}),t&&e.setAttribute("tabindex","-1")},m=e=>"number"==typeof e&&Number.isFinite(e),v=e=>{const t=m(e.speed)&&e.speed>0?e.speed:100,r=m(e.spacing)&&e.spacing>=0?e.spacing:16,n=m(e.delay)&&e.delay>=0?e.delay:0,a=m(e.scrollSpeed)?e.scrollSpeed:2.5,i=Math.min(4,Math.max(1.1,a)),l=-1===e.loop||m(e.loop)&&Number.isInteger(e.loop)&&e.loop>=0?e.loop:-1,o=m(e.maxDuplicates)&&Number.isInteger(e.maxDuplicates)&&e.maxDuplicates>0?e.maxDuplicates:100;return{delay:n,loop:l,maxDuplicates:Math.min(o,250),scrollSpeed:i,spacing:r,speed:t}},h=(...e)=>e.length>0&&e.every(e=>Number.isFinite(e)&&e>0),b=e=>{let t=e;for(;t;){const e=window.getComputedStyle(t).backgroundColor;if(e&&"rgba(0, 0, 0, 0)"!==e&&"transparent"!==e)return e;t=t.parentElement}return"transparent"},y=(e,t,n,a,i)=>{var l;const{spacing:o}=v(i),s=null!==(l=i.fill)&&void 0!==l&&l;r.gsap.set(e,{gap:`${o}px`,flexDirection:a?"column":"row"}),r.gsap.set(t,{flexDirection:a?"column":"row",gap:`${o}px`,[a?"minHeight":"minWidth"]:s?"auto":"100%",flex:s?"0 0 auto":"1"}),r.gsap.set(n,{flexDirection:a?"column":"row",overflow:a?"visible":"hidden"})},x=(e,t)=>t?e.offsetHeight:e.offsetWidth,R=x,S=(e,t,r)=>{const{fill:n=!1}=r,{maxDuplicates:a,spacing:i}=v(r);if(!n||!h(e,t))return{duplicateCount:1,limitReached:!1,requiredDuplicateCount:1};const l=e+i;if(!h(l))return{duplicateCount:1,limitReached:!1,requiredDuplicateCount:1};const o=t+l,s=Math.ceil((o+i)/l),u=Math.max(1,s-1);return{duplicateCount:Math.min(u,a),limitReached:u>a,requiredDuplicateCount:u}},w=(e,t,r)=>S(e,t,r).duplicateCount,q=w,A=(e,t,r)=>{const{fill:n=!1}=r;return n?"auto":e<=t?"100%":`${e}px`},k=A,C=()=>"undefined"!=typeof process&&"production"===process.env.NODE_ENV,E="undefined"==typeof window?n.useEffect:n.useLayoutEffect;let D=!1;const P=({cancelReverseDelay:e,delay:t,dragTrigger:n,enabled:a,isReverse:i,isVertical:l,plugins:o,positionProperty:s,readPaused:u,restoreControlledState:c,scheduleReverseResume:d,timeline:p,trackLength:f})=>{const g=null==o?void 0:o.Draggable;if("function"!=typeof g||!a)return;const m=document.createElement("div"),v=r.gsap.utils.wrap(0,1);let h,b,y;const x={},R=()=>{if(null==y||y.kill(),i&&t>0&&!u())return p.pause(),void(y=d("gsap-react-marquee-drag-delay"));c()},S=()=>{const e=x.instance;if(!e)return;const t=l?e.startY-e.y:e.startX-e.x;p.progress(v(b+t*h))},w=Boolean(r.gsap.plugins.inertia);return w||D||(D=!0,console.warn("InertiaPlugin required for momentum-based scrolling and snapping. https://greensock.com/club")),x.instance=g.create(m,{trigger:n,type:l?"y":"x",onPress(){var t,n;e(),null==y||y.kill(),null===(n=null===(t=x.instance)||void 0===t?void 0:t.tween)||void 0===n||n.kill(),r.gsap.killTweensOf(m),r.gsap.killTweensOf(p),p.pause(),b=p.progress(),h=1/f,r.gsap.set(m,{[s]:b/-h})},onDrag:S,onThrowUpdate:S,overshootTolerance:0,inertia:w,onRelease(){this.isThrowing||R()},onThrowComplete(){R()}})[0],()=>{var e,t,n;null==y||y.kill(),null===(t=null===(e=x.instance)||void 0===e?void 0:e.tween)||void 0===t||t.kill(),null===(n=x.instance)||void 0===n||n.kill(),r.gsap.killTweensOf(m),m.remove()}},z=({timeline:e,isReverse:t,paused:r})=>{e.timeScale(1),r?e.pause():t?e.reverse():e.play()},L=(e,t,n,a,i,l,o,s,u,c)=>{const{spacing:d,speed:p,delay:f,loop:g}=v(o),{paused:m=!1,draggable:b=!1}=o,y=null!=u?u:()=>m;if(0===e.length)return;const x=l?"yPercent":"xPercent",R=l?"y":"x",S=((e,t,n,a,i,l,o,s)=>{const u=[],c=[],d=e.map(e=>s?((e,t,r)=>{let n=0,a=e;for(;a&&a!==t;)n+=r?a.offsetTop:a.offsetLeft,a=a.offsetParent;if(a===t)return n;const i=e.getBoundingClientRect(),l=t.getBoundingClientRect();return r?i.top-l.top:i.left-l.left})(e,s,a):a?e.offsetTop:e.offsetLeft);if(!d.every(Number.isFinite))return null;if(e.forEach((e,t)=>{const n=Number.parseFloat(String(r.gsap.getProperty(e,o,"px"))),a=Number.parseFloat(String(r.gsap.getProperty(e,l,"px"))),s=Number(r.gsap.getProperty(e,i));u[t]=n,c[t]=h(n)&&Number.isFinite(a)&&Number.isFinite(s)?a/n*100+s:Number.NaN}),!u.every(e=>h(e))||!c.every(Number.isFinite))return null;const p=e.length-1,f=e[p],g=d[p],m=a?f.offsetHeight:f.offsetWidth,v=s?d[0]:t,b=g+c[p]/100*u[p]-v+m+n;return Number.isFinite(v)&&h(m,b)?{effectiveStartPosition:v,initialPercents:c,itemOffsets:d,itemSizes:u,trackLength:b}:null})(e,t,d,l,x,R,l?"height":"width",s);if(!S)return;const w=((e,t,r)=>{const{effectiveStartPosition:n,initialPercents:a,itemOffsets:i,itemSizes:l,trackLength:o}=t,s=e.map((e,t)=>{const s=l[t],u=a[t]/100*s,c=i[t]+u-n+s,d=o-c,p=(u-c)/s*100,f=(u-c+o)/s*100,g=c/r,m=d/r;return![u,c,d,p,f,g,m].every(Number.isFinite)||c<0||d<0||g<0||m<0?null:{entryPercent:f,exitDuration:g,exitPercent:p,item:e,returnDuration:m}});return s.every(e=>null!==e)?s:null})(e,S,p);if(!w)return;let q;((e,t,n,a,i)=>{const l=t.map(({item:e})=>e);r.gsap.set(l,{[a]:e=>n[e]}),r.gsap.set(l,{[i]:0}),t.forEach((t,r)=>{e.to(t.item,{[a]:t.exitPercent,duration:t.exitDuration},0).fromTo(t.item,{[a]:t.entryPercent},{[a]:n[r],duration:t.returnDuration,immediateRender:!1},t.exitDuration)})})(n,w,S.initialPercents,x,R);const A=()=>{z({timeline:n,isReverse:a,paused:y()})},k=e=>{const t=r.gsap.delayedCall(f,()=>{A()});return t.data=e,t};n.eventCallback("onReverseComplete",null),-1===g&&n.eventCallback("onReverseComplete",()=>{n.totalTime(n.rawTime()+100*n.duration())}),a?(-1===g?n.progress(1).pause():n.totalProgress(1).pause(),y()||0===f?A():q=k("gsap-react-marquee-reverse-delay")):(n.delay(f),A());const C=P({cancelReverseDelay:()=>null==q?void 0:q.kill(),delay:f,dragTrigger:i,enabled:b,isReverse:a,isVertical:l,plugins:c,positionProperty:R,readPaused:y,restoreControlledState:A,scheduleReverseResume:k,timeline:n,trackLength:S.trackLength});return()=>{null==q||q.kill(),null==C||C(),n.eventCallback("onReverseComplete",null)}},N=L,M=(e,t)=>null!==e&&null!==t&&Number.isFinite(e)&&Number.isFinite(t)&&Math.abs(e-t)<=.5,T=(e,t)=>t?e.offsetHeight:e.offsetWidth,F=e=>e.querySelector(".gsap-react-marquee .gsap-react-marquee-content");let O=null,j=null;const H=e=>{let t=null;return()=>(null!=t||(t=e().catch(e=>{throw t=null,e})),t)},V=H(()=>import("gsap/Observer.js").then(({Observer:e})=>e)),W=H(()=>import("gsap/Draggable.js").then(({Draggable:e})=>({Draggable:e}))),B=new Set,G=e=>{C()||B.has(e)||(B.add(e),Reflect.apply(console.warn,console,[`GSAPReactMarquee: optional GSAP ${e} plugin failed to load; that interactive feature was not initialized.`]))},I="(prefers-reduced-motion: reduce)",$=()=>"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia(I).matches;r.gsap.registerPlugin(t.useGSAP);const U=(...e)=>e.filter(Boolean).join(" "),Y=n.forwardRef((a,i)=>{var o;const{children:u,className:c,containerClassName:d,containerStyle:p,containerProps:m,dir:R="left",loop:q,paused:k=!1,respectReducedMotion:D=!0,delay:P,fill:N=!1,maxDuplicates:H,scrollFollow:B=!1,scrollSpeed:Y,gradient:_=!1,gradientColor:X=null,pauseOnHover:J=!1,spacing:K,speed:Q,draggable:Z=!1}=a,{delay:ee,loop:te,maxDuplicates:re,scrollSpeed:ne,spacing:ae,speed:ie}=v({delay:P,loop:q,maxDuplicates:H,scrollSpeed:Y,spacing:K,speed:Q}),le=n.useRef(null),oe=n.useRef(null),se=(()=>{const[e,t]=n.useState($);return E(()=>{if("function"!=typeof window.matchMedia)return;const e=window.matchMedia(I),r=()=>t(e.matches);return r(),"function"==typeof e.addEventListener?(e.addEventListener("change",r),()=>e.removeEventListener("change",r)):(e.addListener(r),()=>e.removeListener(r))},[]),e})(),ue=D&&se,{pluginsReady:ce,pluginsRef:de}=(({draggable:e,scrollFollow:t})=>{const a=n.useRef({draggable:null,observer:null}),[i,l]=n.useState({draggable:"idle",observer:"idle"}),o=n.useRef(i);return n.useEffect(()=>{let n=!0;const i=(e,t)=>{if(!n)return;const r={...o.current,[e]:t};o.current=r,l(r)},s=e=>{const t=o.current[e];"loading"!==t&&"failed"!==t||i(e,"idle")};return t&&!a.current.observer?(i("observer","loading"),V().then(e=>{n&&((e=>{O!==e&&(r.gsap.registerPlugin(e),O=e)})(e),a.current.observer=e,i("observer","ready"))}).catch(()=>{n&&(i("observer","failed"),G("observer"))})):t||s("observer"),e&&!a.current.draggable?(i("draggable","loading"),W().then(e=>{n&&((e=>{j!==e&&(r.gsap.registerPlugin(e.Draggable),j=e)})(e),a.current.draggable=e,i("draggable","ready"))}).catch(()=>{n&&(i("draggable","failed"),G("draggable"))})):e||s("draggable"),()=>{n=!1}},[e,t]),{pluginStatuses:i,pluginsReady:!(e&&null===a.current.draggable&&"failed"!==i.draggable||t&&null===a.current.observer&&"failed"!==i.observer),pluginsRef:a}})({draggable:Z&&!ue,scrollFollow:B&&!ue}),pe=n.useRef(k);pe.current=k;const[fe,ge]=n.useState(0),[me,ve]=n.useState(null),he=n.useRef(!1),be=n.useRef(!1),ye=n.useCallback(e=>{le.current=e,"function"!=typeof i?i&&(i.current=e):i(e)},[i]);E(()=>{if(!_||!le.current)return;const e=b(le.current);ve(e)},[d,p,_]),E(()=>{var e;!be.current&&oe.current&&((e=oe.current).querySelector(s)||Array.from(e.querySelectorAll("[tabindex]")).some(e=>e.tabIndex>=0))&&(be.current=!0,C()||Reflect.apply(console.warn,console,["GSAPReactMarquee: 0.4.0 supports presentational children only. Interactive children and stable IDs are unsupported; visual clones are sanitized for safety."]))},[u]),E(()=>{const e=le.current;if(!e)return;const t=f();return((e,t=f())=>{Array.from(e.querySelectorAll(l)).forEach(e=>{e.setAttribute("aria-hidden","true"),e.setAttribute("inert",""),t?(e.inert=!0,e.removeAttribute("data-gsap-react-marquee-inert-fallback")):e.setAttribute("data-gsap-react-marquee-inert-fallback",""),g(e),e.querySelectorAll("*").forEach(g)})})(e,t),((e,t=f())=>{if(t)return()=>{};const r=t=>{var r,n;const a=t.target;if(!(a instanceof Element))return;const i=a.closest(l);if(!i||!e.contains(i))return;const o=a;if(null===(r=o.blur)||void 0===r||r.call(o),document.activeElement===a){const e=document.activeElement;null===(n=e.blur)||void 0===n||n.call(e)}};return e.addEventListener("focusin",r),()=>e.removeEventListener("focusin",r)})(e,t)},[u,fe,ue]);const xe="up"===R||"down"===R;E(()=>{const e=le.current;if(!e)return;const t=Array.from(e.querySelectorAll(".gsap-react-marquee")),r=Array.from(e.querySelectorAll(".gsap-react-marquee .gsap-react-marquee-content"));y(e,t,r,xe,{fill:N,spacing:ae})},[fe,N,xe,ae]);const{beginCloneApplication:Re,cloneApplicationSnapshotRef:Se,measurementSnapshotRef:we,measurementVersion:qe}=(({duplicateCount:e,isVertical:t,rootRef:r})=>{const a=n.useRef(!1),i=n.useRef(null),l=n.useRef(null),o=n.useRef(null),[s,u]=n.useState(0),c=n.useCallback(e=>{return!((t=i.current)===(r=e)||t&&r&&t.axis===r.axis&&M(t.contentSize,r.contentSize)&&M(t.rootSize,r.rootSize)&&M(t.viewportSize,r.viewportSize)||(i.current=e,u(e=>e+1),0));var t,r},[]),d=n.useCallback(()=>{const e=r.current;if(!e)return null;const n=F(e);if(!n)return null;const a=T(e,t),i=T(n,t),l=x(e,t);return h(a,i,l)?{axis:t?"vertical":"horizontal",contentSize:i,rootSize:a,viewportSize:l}:null},[t,r]),p=n.useCallback(()=>c(d()),[c,d]),f=n.useCallback(()=>{const e=r.current;if(!e)return c(null);const n=F(e);if(!n)return c(null);const s=t?"vertical":"horizontal",u=T(n,t),d=i.current;if((null==d?void 0:d.axis)===s&&M(u,d.contentSize))return!1;if(a.current=!1,l.current=null,o.current=null,!h(u))return c(null);if((null==d?void 0:d.axis)===s){const r=c({...d,contentSize:u}),n=T(e,t);return o.current=h(n)?n:null,r}return p()},[p,t,c,r]),g=n.useCallback(()=>{const e=r.current;if(!e||a.current)return!1;const n=T(e,t);if(M(n,o.current))return!1;const l=i.current,s=t?"vertical":"horizontal";return((null==l?void 0:l.axis)!==s||!M(n,l.rootSize))&&(o.current=null,p())},[p,t,r]),m=n.useCallback(e=>{const n=r.current,i=n?T(n,t):null;return null===i||M(i,e.rootSize)||M(i,o.current)?(a.current=!0,l.current=e,!0):(p(),!1)},[p,t,r]);return E(()=>{if(!a.current)return;const e=r.current;if(!e)return;const n=T(e,t);o.current=h(n)?n:null;const i=requestAnimationFrame(()=>{a.current=!1,l.current=null});return()=>cancelAnimationFrame(i)},[e,t,r]),E(()=>{const e=r.current;if(!e)return;const n=F(e),s=t?"vertical":"horizontal",u=i.current;u&&u.axis===s?f():(a.current=!1,l.current=null,o.current=null,p());let c=null;const d=()=>{null===c&&(c=requestAnimationFrame(()=>{c=null,f()}))},m="undefined"==typeof ResizeObserver?null:new ResizeObserver(t=>{const r=!!t.some(({target:e})=>e===n)&&f();t.some(({target:t})=>t===e)&&!r&&g()});null==m||m.observe(e),n&&(null==m||m.observe(n));const v=Array.from(e.querySelectorAll("img"));return v.forEach(e=>{e.complete||(e.addEventListener("load",d),e.addEventListener("error",d))}),()=>{null==m||m.disconnect(),v.forEach(e=>{e.removeEventListener("load",d),e.removeEventListener("error",d)}),null!==c&&cancelAnimationFrame(c)}},[f,p,g,t,r]),{beginCloneApplication:m,cloneApplicationSnapshotRef:l,cloneAppliedRootSizeRef:o,isApplyingMeasuredClonesRef:a,measurementSnapshotRef:i,measurementVersion:s}})({duplicateCount:fe,isVertical:xe,rootRef:le});E(()=>{if(ue)return void ge(e=>0===e?e:0);const e=we.current,t=xe?"vertical":"horizontal";if(!e||e.axis!==t)return;const r=S(e.contentSize,e.viewportSize,{fill:N,maxDuplicates:re,spacing:ae}),{duplicateCount:n}=r;r.limitReached&&!he.current&&(he.current=!0,((e,t)=>{C()||Reflect.apply(console.warn,console,[`GSAPReactMarquee: maxDuplicates=${e} prevents full fill coverage; ${t} duplicates are required.`])})(re,r.requiredDuplicateCount)),fe!==n&&Re(e)&&ge(n)},[Re,fe,N,xe,re,we,qe,ue,ae]),(({cloneApplicationSnapshotRef:e,delay:n,dir:a,draggable:i,duplicateCount:l,fill:o,loop:s,marqueeRef:u,maxDuplicates:c,measurementSnapshotRef:d,measurementVersion:p,paused:f,pausedRef:g,pauseOnHover:m,pluginsReady:v,pluginsRef:b,rootRef:y,scrollFollow:x,scrollSpeed:R,shouldReduceMotion:S,spacing:q,speed:k})=>{const C="up"===a||"down"===a,E="down"===a||"right"===a;t.useGSAP((t,a)=>{var p,D;if(S||!u.current||!y.current||!v||!a)return;const P=y.current,N=null!==(p=e.current)&&void 0!==p?p:d.current,M=C?"vertical":"horizontal";if(!N||N.axis!==M)return;const T={fill:o,maxDuplicates:c,spacing:q,speed:k,delay:n,loop:s,paused:f,draggable:i},F=r.gsap.utils.toArray(P.querySelectorAll(".gsap-react-marquee")),O=r.gsap.utils.toArray(P.querySelectorAll(".gsap-react-marquee .gsap-react-marquee-content"));if(!O.length)return;const j=N.rootSize,H=N.contentSize,V=N.viewportSize,W=C?O[0].offsetTop:O[0].offsetLeft;let B=!1,G=!1;const I=()=>g.current||m&&(B||G);if(!h(j,H,V)||!Number.isFinite(W))return;const $=w(H,V,T);if(l!==$)return;const U=o?"auto":A(H,j,T);r.gsap.set(F,{[C?"minHeight":"minWidth"]:U,flex:o?"0 0 auto":"1"});const Y=o?O:F,_=Y.map(e=>C?e.offsetHeight:e.offsetWidth);if(!h(..._))return;const X=r.gsap.timeline({paused:!0,repeat:s,defaults:{ease:"none"},data:"gsap-react-marquee-base"}),J=L(Y,W,X,E,F,C,T,P,I,null!==(D=b.current.draggable)&&void 0!==D?D:void 0);if(!h(X.duration()))return null==J||J(),void X.kill();const K=()=>{z({timeline:X,isReverse:E,paused:I()})},Q=(({enabled:e,isReverse:t,observer:n,restoreBaseTimeline:a,scrollSpeed:i,shouldRemainPaused:l,timeline:o})=>{var s;let u=null;const c=()=>{null==u||u.kill(),u=null,r.gsap.killTweensOf(o)},d=e&&null!==(s=null==n?void 0:n.create({onChangeY(e){if(c(),l())return void a();const n=t?-1:1,s=n*(e.deltaY<0?-1:1)*i*i;u=r.gsap.timeline({data:"gsap-react-marquee-scroll-response",defaults:{ease:"none"},onComplete(){u=null,a()}}).to(o,{timeScale:s,duration:.2,overwrite:!0}).to(o,{timeScale:n,duration:1},"+=0.3")}}))&&void 0!==s?s:null;return{stop:c,cleanup:()=>{null==d||d.kill(),c()}}})({enabled:x,isReverse:E,observer:b.current.observer,restoreBaseTimeline:K,scrollSpeed:R,shouldRemainPaused:I,timeline:X}),Z=a(()=>{B=!0,Q.stop(),X.pause()}),ee=a(()=>{B=!1,K()}),te=a(()=>{G=!0,Q.stop(),X.pause()}),re=a(e=>{const t=e.relatedTarget;t instanceof Node&&P.contains(t)||(G=!1,K())});return m&&(P.addEventListener("pointerenter",Z),P.addEventListener("pointerleave",ee),P.addEventListener("focusin",te),P.addEventListener("focusout",re)),()=>{P.removeEventListener("pointerenter",Z),P.removeEventListener("pointerleave",ee),P.removeEventListener("focusin",te),P.removeEventListener("focusout",re),Q.cleanup(),null==J||J(),r.gsap.killTweensOf(X),X.kill()}},{dependencies:[l,a,s,f,n,o,c,x,R,m,q,k,i,v,p,S],revertOnUpdate:!0})})({cloneApplicationSnapshotRef:Se,delay:ee,dir:R,draggable:Z,duplicateCount:fe,fill:N,loop:te,marqueeRef:oe,maxDuplicates:re,measurementSnapshotRef:we,measurementVersion:qe,paused:k,pausedRef:pe,pauseOnHover:J,pluginsReady:ce,pluginsRef:de,rootRef:le,scrollFollow:B,scrollSpeed:ne,shouldReduceMotion:ue,spacing:ae,speed:ie});const Ae=null!==(o=null!=X?X:_?me:null)&&void 0!==o?o:"transparent",ke=n.useMemo(()=>ue||!Number.isFinite(fe)||fe<=0?null:Array.from({length:fe},(t,r)=>e.jsx("div",{"aria-hidden":"true",className:U("gsap-react-marquee","gsap-react-marquee-clone"),"data-gsap-react-marquee-clone":"",children:e.jsx("div",{className:U("gsap-react-marquee-content",c),children:u})},r)),[fe,c,u,ue]);return e.jsxs("div",{...m,ref:ye,style:{...p,display:"flex",overflow:"hidden",position:"relative",whiteSpace:"nowrap","--gradient-color":Ae},className:U("gsap-react-marquee-container",xe&&"gsap-react-marquee-vertical",d),children:[e.jsx("div",{ref:oe,className:"gsap-react-marquee","data-gsap-react-marquee-original":"",children:e.jsx("div",{className:U("gsap-react-marquee-content",c),children:u})}),ke]})});Y.displayName="GSAPReactMarquee",exports.calculateDuplicateCount=w,exports.calculateDuplicateCountResult=S,exports.calculateDuplicates=q,exports.cn=(...e)=>i.twMerge(a.clsx(e)),exports.coreAnimation=N,exports.createMarqueeAnimation=L,exports.default=Y,exports.getEffectiveBackgroundColor=b,exports.getMinSize=A,exports.getMinWidth=k,exports.getTargetSize=x,exports.getTargetWidth=R,exports.hasDefinedWidth=e=>h(e.offsetWidth),exports.hasUsableMeasurement=h,exports.normalizeMarqueeOptions=v,exports.resumeTimeline=z,exports.setupContainerStyles=y;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties, ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
import { gsap } from 'gsap';
|
|
4
|
+
import { Draggable } from 'gsap/Draggable';
|
|
3
5
|
import { ClassValue } from 'clsx';
|
|
4
6
|
|
|
7
|
+
type DataAttributes = {
|
|
8
|
+
[attribute: `data-${string}`]: boolean | number | string | undefined;
|
|
9
|
+
};
|
|
5
10
|
type GSAPReactMarqueeProps = {
|
|
6
11
|
children: ReactNode;
|
|
7
12
|
className?: string;
|
|
13
|
+
containerClassName?: string;
|
|
14
|
+
containerStyle?: CSSProperties;
|
|
15
|
+
containerProps?: Omit<ComponentPropsWithoutRef<"div">, "children" | "className" | "style" | "ref" | "dangerouslySetInnerHTML"> & DataAttributes;
|
|
8
16
|
dir?: "right" | "left" | "up" | "down";
|
|
9
17
|
loop?: number;
|
|
10
18
|
paused?: boolean;
|
|
19
|
+
respectReducedMotion?: boolean;
|
|
11
20
|
delay?: number;
|
|
12
21
|
speed?: number;
|
|
13
22
|
fill?: boolean;
|
|
23
|
+
maxDuplicates?: number;
|
|
14
24
|
pauseOnHover?: boolean;
|
|
15
25
|
gradient?: boolean;
|
|
16
26
|
gradientColor?: string;
|
|
@@ -22,14 +32,48 @@ type GSAPReactMarqueeProps = {
|
|
|
22
32
|
|
|
23
33
|
declare const GSAPReactMarquee: react.ForwardRefExoticComponent<GSAPReactMarqueeProps & react.RefAttributes<HTMLDivElement>>;
|
|
24
34
|
|
|
35
|
+
type MarqueeDraggablePlugins = {
|
|
36
|
+
Draggable: typeof Draggable;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type GSAPTimeline = ReturnType<typeof gsap.timeline>;
|
|
40
|
+
type ResumeTimelineOptions = {
|
|
41
|
+
timeline: GSAPTimeline;
|
|
42
|
+
isReverse: boolean;
|
|
43
|
+
paused: boolean;
|
|
44
|
+
};
|
|
45
|
+
declare const resumeTimeline: ({ timeline, isReverse, paused, }: ResumeTimelineOptions) => void;
|
|
46
|
+
declare const createMarqueeAnimation: (items: HTMLElement[], startPosition: number, timeline: GSAPTimeline, isReverse: boolean, dragTrigger: HTMLElement | HTMLElement[], isVertical: boolean, props: GSAPReactMarqueeProps, offsetContainer?: HTMLElement, getPaused?: () => boolean, draggablePlugins?: MarqueeDraggablePlugins) => (() => void) | undefined;
|
|
47
|
+
declare const coreAnimation: (items: HTMLElement[], startPosition: number, timeline: GSAPTimeline, isReverse: boolean, dragTrigger: HTMLElement | HTMLElement[], isVertical: boolean, props: GSAPReactMarqueeProps, offsetContainer?: HTMLElement, getPaused?: () => boolean, draggablePlugins?: MarqueeDraggablePlugins) => (() => void) | undefined;
|
|
48
|
+
|
|
49
|
+
declare const hasUsableMeasurement: (...measurements: number[]) => boolean;
|
|
25
50
|
declare const cn: (...inputs: ClassValue[]) => string;
|
|
26
|
-
declare const getEffectiveBackgroundColor: (
|
|
27
|
-
declare const setupContainerStyles: (
|
|
51
|
+
declare const getEffectiveBackgroundColor: (element: HTMLElement) => string;
|
|
52
|
+
declare const setupContainerStyles: (containerElement: HTMLElement, marqueeElements: HTMLElement[], contentElements: HTMLElement[], isVertical: boolean, props: GSAPReactMarqueeProps) => void;
|
|
28
53
|
declare const hasDefinedWidth: (element: HTMLElement) => boolean;
|
|
54
|
+
declare const getTargetSize: (containerElement: HTMLElement, isVertical: boolean) => number;
|
|
29
55
|
declare const getTargetWidth: (containerElement: HTMLElement, isVertical: boolean) => number;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
56
|
+
type DuplicateCountResult = {
|
|
57
|
+
duplicateCount: number;
|
|
58
|
+
limitReached: boolean;
|
|
59
|
+
requiredDuplicateCount: number;
|
|
60
|
+
};
|
|
61
|
+
declare const calculateDuplicateCountResult: (contentSize: number, targetSize: number, props: GSAPReactMarqueeProps) => DuplicateCountResult;
|
|
62
|
+
declare const calculateDuplicateCount: (contentSize: number, targetSize: number, props: GSAPReactMarqueeProps) => number;
|
|
63
|
+
declare const calculateDuplicates: (contentSize: number, targetSize: number, props: GSAPReactMarqueeProps) => number;
|
|
64
|
+
declare const getMinSize: (itemSize: number, containerSize: number, props: GSAPReactMarqueeProps) => string | number;
|
|
65
|
+
declare const getMinWidth: (itemSize: number, containerSize: number, props: GSAPReactMarqueeProps) => string | number;
|
|
66
|
+
|
|
67
|
+
type NormalizedMarqueeOptions = {
|
|
68
|
+
delay: number;
|
|
69
|
+
loop: -1 | number;
|
|
70
|
+
maxDuplicates: number;
|
|
71
|
+
scrollSpeed: number;
|
|
72
|
+
spacing: number;
|
|
73
|
+
speed: number;
|
|
74
|
+
};
|
|
75
|
+
type NumericMarqueeOptions = Pick<GSAPReactMarqueeProps, "delay" | "loop" | "maxDuplicates" | "scrollSpeed" | "spacing" | "speed">;
|
|
76
|
+
declare const normalizeMarqueeOptions: (options: NumericMarqueeOptions) => NormalizedMarqueeOptions;
|
|
33
77
|
|
|
34
|
-
export { calculateDuplicates, cn, coreAnimation, GSAPReactMarquee as default, getEffectiveBackgroundColor, getMinWidth, getTargetWidth, hasDefinedWidth, setupContainerStyles };
|
|
35
|
-
export type { GSAPReactMarqueeProps };
|
|
78
|
+
export { calculateDuplicateCount, calculateDuplicateCountResult, calculateDuplicates, cn, coreAnimation, createMarqueeAnimation, GSAPReactMarquee as default, getEffectiveBackgroundColor, getMinSize, getMinWidth, getTargetSize, getTargetWidth, hasDefinedWidth, hasUsableMeasurement, normalizeMarqueeOptions, resumeTimeline, setupContainerStyles };
|
|
79
|
+
export type { DuplicateCountResult, GSAPReactMarqueeProps, NormalizedMarqueeOptions, ResumeTimelineOptions };
|
package/dist/index.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e,{forwardRef as t,useRef as r,useState as n,useLayoutEffect as o,useMemo as a}from"react";import{useGSAP as i}from"@gsap/react";import l from"gsap";import{Draggable as s,InertiaPlugin as c,Observer as u}from"gsap/all";import{clsx as f}from"clsx";import{twMerge as p}from"tailwind-merge";var d,m,y={exports:{}},g={},v={};"production"===process.env.NODE_ENV?y.exports=function(){if(d)return g;d=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(t,r,n){var o=null;if(void 0!==n&&(o=""+n),void 0!==r.key&&(o=""+r.key),"key"in r)for(var a in n={},r)"key"!==a&&(n[a]=r[a]);else n=r;return r=n.ref,{$$typeof:e,type:t,key:o,ref:void 0!==r?r:null,props:n}}return g.Fragment=t,g.jsx=r,g.jsxs=r,g}():y.exports=(m||(m=1,"production"!==process.env.NODE_ENV&&function(){function t(e){if(null==e)return null;if("function"==typeof e)return e.$$typeof===O?null:e.displayName||e.name||null;if("string"==typeof e)return e;switch(e){case d:return"Fragment";case y:return"Profiler";case m:return"StrictMode";case w:return"Suspense";case S:return"SuspenseList";case _:return"Activity"}if("object"==typeof e)switch(e.tag,e.$$typeof){case p:return"Portal";case h:return(e.displayName||"Context")+".Provider";case g:return(e._context.displayName||"Context")+".Consumer";case b:var r=e.render;return(e=e.displayName)||(e=""!==(e=r.displayName||r.name||"")?"ForwardRef("+e+")":"ForwardRef"),e;case k:return null!==(r=e.displayName||null)?r:t(e.type)||"Memo";case x:r=e._payload,e=e._init;try{return t(e(r))}catch(e){}}return null}function r(e){return""+e}function n(e){try{r(e);var t=!1}catch(e){t=!0}if(t){var n=(t=console).error,o="function"==typeof Symbol&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return n.call(t,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",o),r(e)}}function o(e){if(e===d)return"<>";if("object"==typeof e&&null!==e&&e.$$typeof===x)return"<...>";try{var r=t(e);return r?"<"+r+">":"<...>"}catch(e){return"<...>"}}function a(){return Error("react-stack-top-frame")}function i(){var e=t(this.type);return q[e]||(q[e]=!0),void 0!==(e=this.props.ref)?e:null}function l(e,r,o,a,l,u,p,d){var m,y=r.children;if(void 0!==y)if(a){if(N(y)){for(a=0;a<y.length;a++)s(y[a]);Object.freeze&&Object.freeze(y)}}else s(y);if(E.call(r,"key")){y=t(e);var g=Object.keys(r).filter(function(e){return"key"!==e});a=0<g.length?"{key: someKey, "+g.join(": ..., ")+": ...}":"{key: someKey}",A[y+a]||(g=0<g.length?"{"+g.join(": ..., ")+": ...}":"{}",A[y+a]=!0)}if(y=null,void 0!==o&&(n(o),y=""+o),function(e){if(E.call(e,"key")){var t=Object.getOwnPropertyDescriptor(e,"key").get;if(t&&t.isReactWarning)return!1}return void 0!==e.key}(r)&&(n(r.key),y=""+r.key),"key"in r)for(var v in o={},r)"key"!==v&&(o[v]=r[v]);else o=r;return y&&function(e){function t(){c||(c=!0)}t.isReactWarning=!0,Object.defineProperty(e,"key",{get:t,configurable:!0})}(o,"function"==typeof e&&(e.displayName||e.name)),function(e,t,r,n,o,a,l,s){return r=a.ref,e={$$typeof:f,type:e,key:t,props:a,_owner:o},null!==(void 0!==r?r:null)?Object.defineProperty(e,"ref",{enumerable:!1,get:i}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:l}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:s}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}(e,y,u,0,null===(m=j.A)?null:m.getOwner(),o,p,d)}function s(e){"object"==typeof e&&null!==e&&e.$$typeof===f&&e._store&&(e._store.validated=1)}var c,u=e,f=Symbol.for("react.transitional.element"),p=Symbol.for("react.portal"),d=Symbol.for("react.fragment"),m=Symbol.for("react.strict_mode"),y=Symbol.for("react.profiler"),g=Symbol.for("react.consumer"),h=Symbol.for("react.context"),b=Symbol.for("react.forward_ref"),w=Symbol.for("react.suspense"),S=Symbol.for("react.suspense_list"),k=Symbol.for("react.memo"),x=Symbol.for("react.lazy"),_=Symbol.for("react.activity"),O=Symbol.for("react.client.reference"),j=u.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,E=Object.prototype.hasOwnProperty,N=Array.isArray,T=console.createTask?console.createTask:function(){return null},q={},C=(u={react_stack_bottom_frame:function(e){return e()}}).react_stack_bottom_frame.bind(u,a)(),P=T(o(a)),A={};v.Fragment=d,v.jsx=function(e,t,r,n,a){var i=1e4>j.recentlyCreatedOwnerStacks++;return l(e,t,r,!1,0,a,i?Error("react-stack-top-frame"):C,i?T(o(e)):P)},v.jsxs=function(e,t,r,n,a){var i=1e4>j.recentlyCreatedOwnerStacks++;return l(e,t,r,!0,0,a,i?Error("react-stack-top-frame"):C,i?T(o(e)):P)}}()),v);var h=y.exports;!function(e,t){void 0===t&&(t={});var r=t.insertAt;if("undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css","top"===r&&n.firstChild?n.insertBefore(o,n.firstChild):n.appendChild(o),o.styleSheet?o.styleSheet.cssText=e:o.appendChild(document.createTextNode(e))}}('.gsap-react-marquee-container{display:flex;overflow:hidden;position:relative;white-space:preserve nowrap;width:100%}.gsap-react-marquee-container:after{background:linear-gradient(270deg,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);left:0}.gsap-react-marquee-container:after,.gsap-react-marquee-container:before{content:"";height:100%;pointer-events:none;position:absolute;top:0;width:15%;z-index:10}.gsap-react-marquee-container:before{background:linear-gradient(90deg,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);right:0}.gsap-react-marquee-vertical:after{background:linear-gradient(180deg,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);height:50%;left:0;top:60%;width:100%}.gsap-react-marquee-vertical:before{background:linear-gradient(1turn,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);height:50%;left:0;right:auto;top:0;width:100%}.gsap-react-marquee{flex:1;height:max-content;width:auto}.gsap-react-marquee,.gsap-react-marquee-content{display:flex;line-height:100%;white-space:preserve nowrap}.gsap-react-marquee-content{overflow:hidden;width:max-content}');const b=(...e)=>p(f(e)),w=e=>{let t=e;for(;t;){const e=window.getComputedStyle(t).backgroundColor;if(e&&"rgba(0, 0, 0, 0)"!==e&&"transparent"!==e)return e;t=t.parentElement}return"transparent"},S=(e,t,r,n,o)=>{const{spacing:a=16}=o;l.set(e,{gap:`${a}px`,flexDirection:n?"column":"row"}),l.set(t,{gap:`${a}px`}),n&&l.set(r,{overflow:"visible"})},k=e=>{if(e.style.width&&"auto"!==e.style.width)return!0;const t=window.getComputedStyle(e).width;if("auto"===t||!t)return!1;const r=e.parentElement;if(r){const e=window.getComputedStyle(r);if("100%"===t&&"auto"===e.width)return!1}return!0},x=(e,t)=>k(e)?t?e.offsetHeight:e.offsetWidth:t?window.innerHeight:window.innerWidth,_=(e,t,r)=>r.fill&&e<t?Math.ceil(t/e):1,O=(e,t,r)=>{const{fill:n=!1}=r;return n?"auto":e<t?"100%":e>t?`${e}px`:`${t}px`},j=(e,t,r,n,o,a,i)=>{const{spacing:c=16,speed:u=100,delay:f=0,paused:p=!1}=i,d=[],m=[],y=e.length-1,g=a?"yPercent":"xPercent",v=a?"y":"x",h=a?"height":"width";l.set(e,{[g]:(e,t)=>{const r=d[e]=parseFloat(String(l.getProperty(t,h,"px")));return m[e]=parseFloat(String(l.getProperty(t,v,"px")))/r*100+Number(l.getProperty(t,g)),m[e]}}),l.set(e,{[v]:0});const b=e[y],w=a?b.offsetTop:b.offsetLeft,S=a?b.offsetHeight:b.offsetWidth,k=w+m[y]/100*d[y]-t+S+c;if(e.forEach((e,n)=>{const o=m[n]/100*d[n],i=(a?e.offsetTop:e.offsetLeft)+o-t+d[n];r.to(e,{[g]:(o-i)/d[n]*100,duration:i/u},0).fromTo(e,{[g]:(o-i+k)/d[n]*100},{[g]:m[n],duration:(o-i+k-o)/u,immediateRender:!1},i/u)}),r.delay(f),n){if(p)return void r.pause();r.progress(1).pause(),l.delayedCall(f,()=>{r.reverse(),r.eventCallback("onReverseComplete",()=>{r.totalTime(r.rawTime()+100*r.duration())})})}let x;if("function"==typeof s&&i.draggable){x=document.createElement("div");const e=l.utils.wrap(0,1);let t,i;const c=()=>{const n=a?u.startY-u.y:u.startX-u.x;r.progress(e(i+n*t))},u=s.create(x,{trigger:o,type:a?"y":"x",onPress(){l.killTweensOf(r),r.pause(),i=r.progress(),t=1/k,l.set(x,{[v]:i/-t})},onDrag:c,onThrowUpdate:c,overshootTolerance:0,inertia:!0,onThrowComplete:()=>{if(n){if(p)return void r.pause();r.progress(r.progress()).pause(),l.delayedCall(f,()=>{r.reverse(),r.eventCallback("onReverseComplete",()=>{r.totalTime(r.rawTime()+100*r.duration())})})}else r.play()}})[0]}};l.registerPlugin(i,u,c,s);const E=t((e,t)=>{const{children:s,className:c,dir:f="left",loop:p=-1,paused:d=!1,fill:m=!1,scrollFollow:y=!1,scrollSpeed:g=2.5,gradient:v=!1,gradientColor:k=null,pauseOnHover:x=!1,spacing:E=16,speed:N=100}=e,T=r(null),q=t||T,C=r(null),[P,A]=n(1),[$,R]=n(null),[L,F]=n(0);o(()=>{if(!v||!(null==q?void 0:q.current))return;const e=w(q.current);R(e)},[v]);const W="up"===f||"down"===f,H="down"===f||"right"===f;o(()=>{const e=q.current;if(!e)return;const t=e.querySelector(".gsap-react-marquee .gsap-react-marquee-content");let r=null;const n=()=>{null==r&&(r=requestAnimationFrame(()=>{F(e=>e+1),r=null}))},o=t?new ResizeObserver(n):null;t&&o&&o.observe(t);const a=Array.from(e.querySelectorAll("img")),i=()=>n();return a.forEach(e=>{e.complete||(e.addEventListener("load",i),e.addEventListener("error",i))}),()=>{null==o||o.disconnect(),a.forEach(e=>{e.removeEventListener("load",i),e.removeEventListener("error",i)}),null!=r&&cancelAnimationFrame(r)}},[s]),i((t,r)=>{if(!(null==C?void 0:C.current)||!q.current||!r)return;const n=null==q?void 0:q.current,o=l.utils.toArray(n.querySelectorAll(".gsap-react-marquee")),a=l.utils.toArray(n.querySelectorAll(".gsap-react-marquee .gsap-react-marquee-content"));if(!C.current||!a)return;const i=l.timeline({paused:d,repeat:p,defaults:{ease:"none"},onReverseComplete(){i.totalTime(i.rawTime()+100*i.duration())}});S(n,o,a,W,e);const s=W?n.offsetHeight:n.offsetWidth,c=W?a[0].offsetHeight:a[0].offsetWidth,f=W?a[0].offsetTop:a[0].offsetLeft;let v=null;const h=Math.min(4,Math.max(1.1,g));A(_(c,s,e));const b=l.utils.toArray(o).map(e=>W?e.offsetHeight:e.offsetWidth).reduce((e,t)=>e+t,0),w=O(b/(1===P?2:P),s,e);l.set(o,{[W?"minHeight":"minWidth"]:w,flex:m?"0 0 auto":"1"}),j(m?a:o,f,i,H,o,W,e),y&&(v=u.create({onChangeY(e){let t=h*(H?-1:1);e.deltaY<0&&(t*=-1),l.timeline({defaults:{ease:"none"}}).to(i,{timeScale:t*h,duration:.2,overwrite:!0}).to(i,{timeScale:t/h,duration:1},"+=0.3")}}));const k=r(()=>{i.pause()}),E=r(()=>{H?i.reverse():i.play()});return x&&(n.addEventListener("mouseenter",k),n.addEventListener("mouseleave",E)),()=>{n.removeEventListener("mouseenter",k),n.removeEventListener("mouseleave",E),i.kill(),null==v||v.kill()}},{dependencies:[P,f,p,d,m,y,g,v,k,x,E,N,s,L],revertOnUpdate:!0});const z=a(()=>!Number.isFinite(P)||P<=0?null:Array.from({length:P},(e,t)=>h.jsx("div",{className:b("gsap-react-marquee"),children:h.jsx("div",{className:b("gsap-react-marquee-content",c),children:s})},t)),[P,c,s]);return h.jsxs("div",{ref:q,style:{"--gradient-color":k||(v&&$?$:"transparent")},className:b("gsap-react-marquee-container",{"gsap-react-marquee-vertical":W}),children:[h.jsx("div",{ref:C,className:b("gsap-react-marquee"),children:h.jsx("div",{className:b("gsap-react-marquee-content",c),children:s})}),z]})});E.displayName="GSAPReactMarquee";export{_ as calculateDuplicates,b as cn,j as coreAnimation,E as default,w as getEffectiveBackgroundColor,O as getMinWidth,x as getTargetWidth,k as hasDefinedWidth,S as setupContainerStyles};
|
|
1
|
+
import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useGSAP as r}from"@gsap/react";import{gsap as n}from"gsap";import{useEffect as a,useLayoutEffect as i,useRef as l,useState as o,useCallback as s,forwardRef as u,useMemo as c}from"react";import{clsx as d}from"clsx";import{twMerge as p}from"tailwind-merge";if("undefined"!=typeof document&&!document.getElementById("gsap-react-marquee-styles")){const e=document.createElement("style");e.id="gsap-react-marquee-styles",e.textContent='.gsap-react-marquee-container{white-space:nowrap;width:100%;display:flex;position:relative;overflow:hidden}:where(.gsap-react-marquee-container.gsap-react-marquee-vertical){align-self:stretch;height:100%;min-height:0}.gsap-react-marquee-container:after{content:"";background:linear-gradient(270deg, #fff0 0%, var(--gradient-color) 75%);z-index:10;pointer-events:none;width:15%;height:100%;position:absolute;top:0;left:0}.gsap-react-marquee-container:before{content:"";background:linear-gradient(90deg, #fff0 0%, var(--gradient-color) 75%);z-index:10;pointer-events:none;width:15%;height:100%;position:absolute;top:0;right:0}.gsap-react-marquee-vertical:after{background:linear-gradient(180deg, #fff0 0%, var(--gradient-color) 75%);width:100%;height:50%;top:60%;left:0}.gsap-react-marquee-vertical:before{background:linear-gradient(360deg, #fff0 0%, var(--gradient-color) 75%);width:100%;height:50%;top:0;left:0;right:auto}.gsap-react-marquee{white-space:nowrap;flex:1;width:auto;height:max-content;line-height:100%;display:flex}.gsap-react-marquee-clone,.gsap-react-marquee-clone *{pointer-events:none}.gsap-react-marquee-content{white-space:nowrap;width:max-content;line-height:100%;display:flex;overflow:hidden}',document.head.appendChild(e)}const f="[data-gsap-react-marquee-clone]",m=["a[href]","area[href]","audio[controls]","button","embed","iframe","input:not([type='hidden'])","object","select","summary","textarea","video[controls]","[contenteditable]:not([contenteditable='false'])","[tabindex]"].join(","),g=["[id]","a[href]","area[href]","button","embed","iframe","input","object","select","textarea","[contenteditable]:not([contenteditable='false'])"].join(","),v=["aria-activedescendant","aria-controls","aria-describedby","aria-details","aria-errormessage","aria-flowto","aria-labelledby","aria-owns","for","form","headers","itemref","list"],h=["clip-path","fill","filter","marker-end","marker-mid","marker-start","mask","stroke"],b=/^\s*#/,y=/url\(\s*["']?#/i,w=()=>"undefined"!=typeof HTMLElement&&"inert"in HTMLElement.prototype,S=e=>{const t=e.matches(m);e.removeAttribute("id"),e.removeAttribute("name"),v.forEach(t=>{e.removeAttribute(t)});const r=e.getAttribute("href");r&&b.test(r)&&e.removeAttribute("href");const n=e.getAttribute("xlink:href");n&&b.test(n)&&e.removeAttribute("xlink:href"),h.forEach(t=>{const r=e.getAttribute(t);r&&y.test(r)&&e.removeAttribute(t)}),e instanceof HTMLElement&&Array.from(e.style).forEach(t=>{y.test(e.style.getPropertyValue(t))&&e.style.removeProperty(t)}),t&&e.setAttribute("tabindex","-1")},x=e=>"number"==typeof e&&Number.isFinite(e),R=e=>{const t=x(e.speed)&&e.speed>0?e.speed:100,r=x(e.spacing)&&e.spacing>=0?e.spacing:16,n=x(e.delay)&&e.delay>=0?e.delay:0,a=x(e.scrollSpeed)?e.scrollSpeed:2.5,i=Math.min(4,Math.max(1.1,a)),l=-1===e.loop||x(e.loop)&&Number.isInteger(e.loop)&&e.loop>=0?e.loop:-1,o=x(e.maxDuplicates)&&Number.isInteger(e.maxDuplicates)&&e.maxDuplicates>0?e.maxDuplicates:100;return{delay:n,loop:l,maxDuplicates:Math.min(o,250),scrollSpeed:i,spacing:r,speed:t}},q=(...e)=>e.length>0&&e.every(e=>Number.isFinite(e)&&e>0),A=(...e)=>p(d(e)),k=e=>{let t=e;for(;t;){const e=window.getComputedStyle(t).backgroundColor;if(e&&"rgba(0, 0, 0, 0)"!==e&&"transparent"!==e)return e;t=t.parentElement}return"transparent"},E=(e,t,r,a,i)=>{var l;const{spacing:o}=R(i),s=null!==(l=i.fill)&&void 0!==l&&l;n.set(e,{gap:`${o}px`,flexDirection:a?"column":"row"}),n.set(t,{flexDirection:a?"column":"row",gap:`${o}px`,[a?"minHeight":"minWidth"]:s?"auto":"100%",flex:s?"0 0 auto":"1"}),n.set(r,{flexDirection:a?"column":"row",overflow:a?"visible":"hidden"})},C=e=>q(e.offsetWidth),D=(e,t)=>t?e.offsetHeight:e.offsetWidth,P=D,z=(e,t,r)=>{const{fill:n=!1}=r,{maxDuplicates:a,spacing:i}=R(r);if(!n||!q(e,t))return{duplicateCount:1,limitReached:!1,requiredDuplicateCount:1};const l=e+i;if(!q(l))return{duplicateCount:1,limitReached:!1,requiredDuplicateCount:1};const o=t+l,s=Math.ceil((o+i)/l),u=Math.max(1,s-1);return{duplicateCount:Math.min(u,a),limitReached:u>a,requiredDuplicateCount:u}},N=(e,t,r)=>z(e,t,r).duplicateCount,L=N,F=(e,t,r)=>{const{fill:n=!1}=r;return n?"auto":e<=t?"100%":`${e}px`},T=F,M=()=>"undefined"!=typeof process&&"production"===process.env.NODE_ENV,O="undefined"==typeof window?a:i;let H=!1;const V=({cancelReverseDelay:e,delay:t,dragTrigger:r,enabled:a,isReverse:i,isVertical:l,plugins:o,positionProperty:s,readPaused:u,restoreControlledState:c,scheduleReverseResume:d,timeline:p,trackLength:f})=>{const m=null==o?void 0:o.Draggable;if("function"!=typeof m||!a)return;const g=document.createElement("div"),v=n.utils.wrap(0,1);let h,b,y;const w={},S=()=>{if(null==y||y.kill(),i&&t>0&&!u())return p.pause(),void(y=d("gsap-react-marquee-drag-delay"));c()},x=()=>{const e=w.instance;if(!e)return;const t=l?e.startY-e.y:e.startX-e.x;p.progress(v(b+t*h))},R=Boolean(n.plugins.inertia);return R||H||(H=!0,console.warn("InertiaPlugin required for momentum-based scrolling and snapping. https://greensock.com/club")),w.instance=m.create(g,{trigger:r,type:l?"y":"x",onPress(){var t,r;e(),null==y||y.kill(),null===(r=null===(t=w.instance)||void 0===t?void 0:t.tween)||void 0===r||r.kill(),n.killTweensOf(g),n.killTweensOf(p),p.pause(),b=p.progress(),h=1/f,n.set(g,{[s]:b/-h})},onDrag:x,onThrowUpdate:x,overshootTolerance:0,inertia:R,onRelease(){this.isThrowing||S()},onThrowComplete(){S()}})[0],()=>{var e,t,r;null==y||y.kill(),null===(t=null===(e=w.instance)||void 0===e?void 0:e.tween)||void 0===t||t.kill(),null===(r=w.instance)||void 0===r||r.kill(),n.killTweensOf(g),g.remove()}},j=({timeline:e,isReverse:t,paused:r})=>{e.timeScale(1),r?e.pause():t?e.reverse():e.play()},B=(e,t,r,a,i,l,o,s,u,c)=>{const{spacing:d,speed:p,delay:f,loop:m}=R(o),{paused:g=!1,draggable:v=!1}=o,h=null!=u?u:()=>g;if(0===e.length)return;const b=l?"yPercent":"xPercent",y=l?"y":"x",w=((e,t,r,a,i,l,o,s)=>{const u=[],c=[],d=e.map(e=>s?((e,t,r)=>{let n=0,a=e;for(;a&&a!==t;)n+=r?a.offsetTop:a.offsetLeft,a=a.offsetParent;if(a===t)return n;const i=e.getBoundingClientRect(),l=t.getBoundingClientRect();return r?i.top-l.top:i.left-l.left})(e,s,a):a?e.offsetTop:e.offsetLeft);if(!d.every(Number.isFinite))return null;if(e.forEach((e,t)=>{const r=Number.parseFloat(String(n.getProperty(e,o,"px"))),a=Number.parseFloat(String(n.getProperty(e,l,"px"))),s=Number(n.getProperty(e,i));u[t]=r,c[t]=q(r)&&Number.isFinite(a)&&Number.isFinite(s)?a/r*100+s:Number.NaN}),!u.every(e=>q(e))||!c.every(Number.isFinite))return null;const p=e.length-1,f=e[p],m=d[p],g=a?f.offsetHeight:f.offsetWidth,v=s?d[0]:t,h=m+c[p]/100*u[p]-v+g+r;return Number.isFinite(v)&&q(g,h)?{effectiveStartPosition:v,initialPercents:c,itemOffsets:d,itemSizes:u,trackLength:h}:null})(e,t,d,l,b,y,l?"height":"width",s);if(!w)return;const S=((e,t,r)=>{const{effectiveStartPosition:n,initialPercents:a,itemOffsets:i,itemSizes:l,trackLength:o}=t,s=e.map((e,t)=>{const s=l[t],u=a[t]/100*s,c=i[t]+u-n+s,d=o-c,p=(u-c)/s*100,f=(u-c+o)/s*100,m=c/r,g=d/r;return![u,c,d,p,f,m,g].every(Number.isFinite)||c<0||d<0||m<0||g<0?null:{entryPercent:f,exitDuration:m,exitPercent:p,item:e,returnDuration:g}});return s.every(e=>null!==e)?s:null})(e,w,p);if(!S)return;let x;((e,t,r,a,i)=>{const l=t.map(({item:e})=>e);n.set(l,{[a]:e=>r[e]}),n.set(l,{[i]:0}),t.forEach((t,n)=>{e.to(t.item,{[a]:t.exitPercent,duration:t.exitDuration},0).fromTo(t.item,{[a]:t.entryPercent},{[a]:r[n],duration:t.returnDuration,immediateRender:!1},t.exitDuration)})})(r,S,w.initialPercents,b,y);const A=()=>{j({timeline:r,isReverse:a,paused:h()})},k=e=>{const t=n.delayedCall(f,()=>{A()});return t.data=e,t};r.eventCallback("onReverseComplete",null),-1===m&&r.eventCallback("onReverseComplete",()=>{r.totalTime(r.rawTime()+100*r.duration())}),a?(-1===m?r.progress(1).pause():r.totalProgress(1).pause(),h()||0===f?A():x=k("gsap-react-marquee-reverse-delay")):(r.delay(f),A());const E=V({cancelReverseDelay:()=>null==x?void 0:x.kill(),delay:f,dragTrigger:i,enabled:v,isReverse:a,isVertical:l,plugins:c,positionProperty:y,readPaused:h,restoreControlledState:A,scheduleReverseResume:k,timeline:r,trackLength:w.trackLength});return()=>{null==x||x.kill(),null==E||E(),r.eventCallback("onReverseComplete",null)}},I=B,W=(e,t)=>null!==e&&null!==t&&Number.isFinite(e)&&Number.isFinite(t)&&Math.abs(e-t)<=.5,$=(e,t)=>t?e.offsetHeight:e.offsetWidth,G=e=>e.querySelector(".gsap-react-marquee .gsap-react-marquee-content");let Y=null,U=null;const X=e=>{let t=null;return()=>(null!=t||(t=e().catch(e=>{throw t=null,e})),t)},_=X(()=>import("gsap/Observer.js").then(({Observer:e})=>e)),J=X(()=>import("gsap/Draggable.js").then(({Draggable:e})=>({Draggable:e}))),K=new Set,Q=e=>{M()||K.has(e)||(K.add(e),Reflect.apply(console.warn,console,[`GSAPReactMarquee: optional GSAP ${e} plugin failed to load; that interactive feature was not initialized.`]))},Z="(prefers-reduced-motion: reduce)",ee=()=>"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia(Z).matches;n.registerPlugin(r);const te=(...e)=>e.filter(Boolean).join(" "),re=u((i,u)=>{var d;const{children:p,className:m,containerClassName:v,containerStyle:h,containerProps:b,dir:y="left",loop:x,paused:A=!1,respectReducedMotion:C=!0,delay:P,fill:L=!1,maxDuplicates:T,scrollFollow:H=!1,scrollSpeed:V,gradient:I=!1,gradientColor:X=null,pauseOnHover:K=!1,spacing:re,speed:ne,draggable:ae=!1}=i,{delay:ie,loop:le,maxDuplicates:oe,scrollSpeed:se,spacing:ue,speed:ce}=R({delay:P,loop:x,maxDuplicates:T,scrollSpeed:V,spacing:re,speed:ne}),de=l(null),pe=l(null),fe=(()=>{const[e,t]=o(ee);return O(()=>{if("function"!=typeof window.matchMedia)return;const e=window.matchMedia(Z),r=()=>t(e.matches);return r(),"function"==typeof e.addEventListener?(e.addEventListener("change",r),()=>e.removeEventListener("change",r)):(e.addListener(r),()=>e.removeListener(r))},[]),e})(),me=C&&fe,{pluginsReady:ge,pluginsRef:ve}=(({draggable:e,scrollFollow:t})=>{const r=l({draggable:null,observer:null}),[i,s]=o({draggable:"idle",observer:"idle"}),u=l(i);return a(()=>{let a=!0;const i=(e,t)=>{if(!a)return;const r={...u.current,[e]:t};u.current=r,s(r)},l=e=>{const t=u.current[e];"loading"!==t&&"failed"!==t||i(e,"idle")};return t&&!r.current.observer?(i("observer","loading"),_().then(e=>{a&&((e=>{Y!==e&&(n.registerPlugin(e),Y=e)})(e),r.current.observer=e,i("observer","ready"))}).catch(()=>{a&&(i("observer","failed"),Q("observer"))})):t||l("observer"),e&&!r.current.draggable?(i("draggable","loading"),J().then(e=>{a&&((e=>{U!==e&&(n.registerPlugin(e.Draggable),U=e)})(e),r.current.draggable=e,i("draggable","ready"))}).catch(()=>{a&&(i("draggable","failed"),Q("draggable"))})):e||l("draggable"),()=>{a=!1}},[e,t]),{pluginStatuses:i,pluginsReady:!(e&&null===r.current.draggable&&"failed"!==i.draggable||t&&null===r.current.observer&&"failed"!==i.observer),pluginsRef:r}})({draggable:ae&&!me,scrollFollow:H&&!me}),he=l(A);he.current=A;const[be,ye]=o(0),[we,Se]=o(null),xe=l(!1),Re=l(!1),qe=s(e=>{de.current=e,"function"!=typeof u?u&&(u.current=e):u(e)},[u]);O(()=>{if(!I||!de.current)return;const e=k(de.current);Se(e)},[v,h,I]),O(()=>{var e;!Re.current&&pe.current&&((e=pe.current).querySelector(g)||Array.from(e.querySelectorAll("[tabindex]")).some(e=>e.tabIndex>=0))&&(Re.current=!0,M()||Reflect.apply(console.warn,console,["GSAPReactMarquee: 0.4.0 supports presentational children only. Interactive children and stable IDs are unsupported; visual clones are sanitized for safety."]))},[p]),O(()=>{const e=de.current;if(!e)return;const t=w();return((e,t=w())=>{Array.from(e.querySelectorAll(f)).forEach(e=>{e.setAttribute("aria-hidden","true"),e.setAttribute("inert",""),t?(e.inert=!0,e.removeAttribute("data-gsap-react-marquee-inert-fallback")):e.setAttribute("data-gsap-react-marquee-inert-fallback",""),S(e),e.querySelectorAll("*").forEach(S)})})(e,t),((e,t=w())=>{if(t)return()=>{};const r=t=>{var r,n;const a=t.target;if(!(a instanceof Element))return;const i=a.closest(f);if(!i||!e.contains(i))return;const l=a;if(null===(r=l.blur)||void 0===r||r.call(l),document.activeElement===a){const e=document.activeElement;null===(n=e.blur)||void 0===n||n.call(e)}};return e.addEventListener("focusin",r),()=>e.removeEventListener("focusin",r)})(e,t)},[p,be,me]);const Ae="up"===y||"down"===y;O(()=>{const e=de.current;if(!e)return;const t=Array.from(e.querySelectorAll(".gsap-react-marquee")),r=Array.from(e.querySelectorAll(".gsap-react-marquee .gsap-react-marquee-content"));E(e,t,r,Ae,{fill:L,spacing:ue})},[be,L,Ae,ue]);const{beginCloneApplication:ke,cloneApplicationSnapshotRef:Ee,measurementSnapshotRef:Ce,measurementVersion:De}=(({duplicateCount:e,isVertical:t,rootRef:r})=>{const n=l(!1),a=l(null),i=l(null),u=l(null),[c,d]=o(0),p=s(e=>{return!((t=a.current)===(r=e)||t&&r&&t.axis===r.axis&&W(t.contentSize,r.contentSize)&&W(t.rootSize,r.rootSize)&&W(t.viewportSize,r.viewportSize)||(a.current=e,d(e=>e+1),0));var t,r},[]),f=s(()=>{const e=r.current;if(!e)return null;const n=G(e);if(!n)return null;const a=$(e,t),i=$(n,t),l=D(e,t);return q(a,i,l)?{axis:t?"vertical":"horizontal",contentSize:i,rootSize:a,viewportSize:l}:null},[t,r]),m=s(()=>p(f()),[p,f]),g=s(()=>{const e=r.current;if(!e)return p(null);const l=G(e);if(!l)return p(null);const o=t?"vertical":"horizontal",s=$(l,t),c=a.current;if((null==c?void 0:c.axis)===o&&W(s,c.contentSize))return!1;if(n.current=!1,i.current=null,u.current=null,!q(s))return p(null);if((null==c?void 0:c.axis)===o){const r=p({...c,contentSize:s}),n=$(e,t);return u.current=q(n)?n:null,r}return m()},[m,t,p,r]),v=s(()=>{const e=r.current;if(!e||n.current)return!1;const i=$(e,t);if(W(i,u.current))return!1;const l=a.current,o=t?"vertical":"horizontal";return((null==l?void 0:l.axis)!==o||!W(i,l.rootSize))&&(u.current=null,m())},[m,t,r]),h=s(e=>{const a=r.current,l=a?$(a,t):null;return null===l||W(l,e.rootSize)||W(l,u.current)?(n.current=!0,i.current=e,!0):(m(),!1)},[m,t,r]);return O(()=>{if(!n.current)return;const e=r.current;if(!e)return;const a=$(e,t);u.current=q(a)?a:null;const l=requestAnimationFrame(()=>{n.current=!1,i.current=null});return()=>cancelAnimationFrame(l)},[e,t,r]),O(()=>{const e=r.current;if(!e)return;const l=G(e),o=t?"vertical":"horizontal",s=a.current;s&&s.axis===o?g():(n.current=!1,i.current=null,u.current=null,m());let c=null;const d=()=>{null===c&&(c=requestAnimationFrame(()=>{c=null,g()}))},p="undefined"==typeof ResizeObserver?null:new ResizeObserver(t=>{const r=!!t.some(({target:e})=>e===l)&&g();t.some(({target:t})=>t===e)&&!r&&v()});null==p||p.observe(e),l&&(null==p||p.observe(l));const f=Array.from(e.querySelectorAll("img"));return f.forEach(e=>{e.complete||(e.addEventListener("load",d),e.addEventListener("error",d))}),()=>{null==p||p.disconnect(),f.forEach(e=>{e.removeEventListener("load",d),e.removeEventListener("error",d)}),null!==c&&cancelAnimationFrame(c)}},[g,m,v,t,r]),{beginCloneApplication:h,cloneApplicationSnapshotRef:i,cloneAppliedRootSizeRef:u,isApplyingMeasuredClonesRef:n,measurementSnapshotRef:a,measurementVersion:c}})({duplicateCount:be,isVertical:Ae,rootRef:de});O(()=>{if(me)return void ye(e=>0===e?e:0);const e=Ce.current,t=Ae?"vertical":"horizontal";if(!e||e.axis!==t)return;const r=z(e.contentSize,e.viewportSize,{fill:L,maxDuplicates:oe,spacing:ue}),{duplicateCount:n}=r;r.limitReached&&!xe.current&&(xe.current=!0,((e,t)=>{M()||Reflect.apply(console.warn,console,[`GSAPReactMarquee: maxDuplicates=${e} prevents full fill coverage; ${t} duplicates are required.`])})(oe,r.requiredDuplicateCount)),be!==n&&ke(e)&&ye(n)},[ke,be,L,Ae,oe,Ce,De,me,ue]),(({cloneApplicationSnapshotRef:e,delay:t,dir:a,draggable:i,duplicateCount:l,fill:o,loop:s,marqueeRef:u,maxDuplicates:c,measurementSnapshotRef:d,measurementVersion:p,paused:f,pausedRef:m,pauseOnHover:g,pluginsReady:v,pluginsRef:h,rootRef:b,scrollFollow:y,scrollSpeed:w,shouldReduceMotion:S,spacing:x,speed:R})=>{const A="up"===a||"down"===a,k="down"===a||"right"===a;r((r,a)=>{var p,E;if(S||!u.current||!b.current||!v||!a)return;const C=b.current,D=null!==(p=e.current)&&void 0!==p?p:d.current,P=A?"vertical":"horizontal";if(!D||D.axis!==P)return;const z={fill:o,maxDuplicates:c,spacing:x,speed:R,delay:t,loop:s,paused:f,draggable:i},L=n.utils.toArray(C.querySelectorAll(".gsap-react-marquee")),T=n.utils.toArray(C.querySelectorAll(".gsap-react-marquee .gsap-react-marquee-content"));if(!T.length)return;const M=D.rootSize,O=D.contentSize,H=D.viewportSize,V=A?T[0].offsetTop:T[0].offsetLeft;let I=!1,W=!1;const $=()=>m.current||g&&(I||W);if(!q(M,O,H)||!Number.isFinite(V))return;const G=N(O,H,z);if(l!==G)return;const Y=o?"auto":F(O,M,z);n.set(L,{[A?"minHeight":"minWidth"]:Y,flex:o?"0 0 auto":"1"});const U=o?T:L,X=U.map(e=>A?e.offsetHeight:e.offsetWidth);if(!q(...X))return;const _=n.timeline({paused:!0,repeat:s,defaults:{ease:"none"},data:"gsap-react-marquee-base"}),J=B(U,V,_,k,L,A,z,C,$,null!==(E=h.current.draggable)&&void 0!==E?E:void 0);if(!q(_.duration()))return null==J||J(),void _.kill();const K=()=>{j({timeline:_,isReverse:k,paused:$()})},Q=(({enabled:e,isReverse:t,observer:r,restoreBaseTimeline:a,scrollSpeed:i,shouldRemainPaused:l,timeline:o})=>{var s;let u=null;const c=()=>{null==u||u.kill(),u=null,n.killTweensOf(o)},d=e&&null!==(s=null==r?void 0:r.create({onChangeY(e){if(c(),l())return void a();const r=t?-1:1,s=r*(e.deltaY<0?-1:1)*i*i;u=n.timeline({data:"gsap-react-marquee-scroll-response",defaults:{ease:"none"},onComplete(){u=null,a()}}).to(o,{timeScale:s,duration:.2,overwrite:!0}).to(o,{timeScale:r,duration:1},"+=0.3")}}))&&void 0!==s?s:null;return{stop:c,cleanup:()=>{null==d||d.kill(),c()}}})({enabled:y,isReverse:k,observer:h.current.observer,restoreBaseTimeline:K,scrollSpeed:w,shouldRemainPaused:$,timeline:_}),Z=a(()=>{I=!0,Q.stop(),_.pause()}),ee=a(()=>{I=!1,K()}),te=a(()=>{W=!0,Q.stop(),_.pause()}),re=a(e=>{const t=e.relatedTarget;t instanceof Node&&C.contains(t)||(W=!1,K())});return g&&(C.addEventListener("pointerenter",Z),C.addEventListener("pointerleave",ee),C.addEventListener("focusin",te),C.addEventListener("focusout",re)),()=>{C.removeEventListener("pointerenter",Z),C.removeEventListener("pointerleave",ee),C.removeEventListener("focusin",te),C.removeEventListener("focusout",re),Q.cleanup(),null==J||J(),n.killTweensOf(_),_.kill()}},{dependencies:[l,a,s,f,t,o,c,y,w,g,x,R,i,v,p,S],revertOnUpdate:!0})})({cloneApplicationSnapshotRef:Ee,delay:ie,dir:y,draggable:ae,duplicateCount:be,fill:L,loop:le,marqueeRef:pe,maxDuplicates:oe,measurementSnapshotRef:Ce,measurementVersion:De,paused:A,pausedRef:he,pauseOnHover:K,pluginsReady:ge,pluginsRef:ve,rootRef:de,scrollFollow:H,scrollSpeed:se,shouldReduceMotion:me,spacing:ue,speed:ce});const Pe=null!==(d=null!=X?X:I?we:null)&&void 0!==d?d:"transparent",ze=c(()=>me||!Number.isFinite(be)||be<=0?null:Array.from({length:be},(t,r)=>e("div",{"aria-hidden":"true",className:te("gsap-react-marquee","gsap-react-marquee-clone"),"data-gsap-react-marquee-clone":"",children:e("div",{className:te("gsap-react-marquee-content",m),children:p})},r)),[be,m,p,me]);return t("div",{...b,ref:qe,style:{...h,display:"flex",overflow:"hidden",position:"relative",whiteSpace:"nowrap","--gradient-color":Pe},className:te("gsap-react-marquee-container",Ae&&"gsap-react-marquee-vertical",v),children:[e("div",{ref:pe,className:"gsap-react-marquee","data-gsap-react-marquee-original":"",children:e("div",{className:te("gsap-react-marquee-content",m),children:p})}),ze]})});re.displayName="GSAPReactMarquee";export{N as calculateDuplicateCount,z as calculateDuplicateCountResult,L as calculateDuplicates,A as cn,I as coreAnimation,B as createMarqueeAnimation,re as default,k as getEffectiveBackgroundColor,F as getMinSize,T as getMinWidth,D as getTargetSize,P as getTargetWidth,C as hasDefinedWidth,q as hasUsableMeasurement,R as normalizeMarqueeOptions,j as resumeTimeline,E as setupContainerStyles};
|
package/package.json
CHANGED
|
@@ -3,26 +3,45 @@
|
|
|
3
3
|
"author": "David Domenico Piscopo",
|
|
4
4
|
"description": "A high-performance React marquee component powered by GSAP",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.4.0",
|
|
7
|
+
"packageManager": "pnpm@10.34.5",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20"
|
|
10
|
+
},
|
|
7
11
|
"type": "module",
|
|
8
|
-
"main": "dist/index.cjs
|
|
12
|
+
"main": "dist/index.cjs",
|
|
9
13
|
"module": "dist/index.esm.js",
|
|
10
14
|
"types": "dist/index.d.ts",
|
|
11
15
|
"exports": {
|
|
12
16
|
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
13
18
|
"import": "./dist/index.esm.js",
|
|
14
|
-
"require": "./dist/index.cjs
|
|
15
|
-
"types": "./dist/index.d.ts"
|
|
19
|
+
"require": "./dist/index.cjs"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"files": [
|
|
19
|
-
"dist",
|
|
23
|
+
"dist/index.cjs",
|
|
24
|
+
"dist/index.esm.js",
|
|
25
|
+
"dist/index.d.ts",
|
|
20
26
|
"README.md",
|
|
21
27
|
"LICENSE"
|
|
22
28
|
],
|
|
23
29
|
"scripts": {
|
|
24
|
-
"
|
|
25
|
-
"build
|
|
30
|
+
"clean": "node scripts/clean-dist.mjs",
|
|
31
|
+
"build": "pnpm run clean && rollup -c && node scripts/clean-dist.mjs --declarations-only",
|
|
32
|
+
"build:watch": "pnpm run clean && rollup -c -w",
|
|
33
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json --noEmit",
|
|
34
|
+
"lint": "eslint .",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"test:coverage": "vitest run --coverage",
|
|
38
|
+
"test:browser": "playwright test --project=chromium",
|
|
39
|
+
"test:browser:firefox": "playwright test --project=firefox",
|
|
40
|
+
"test:browser:webkit": "playwright test --project=webkit",
|
|
41
|
+
"test:package": "pnpm run build && node tests/package/run-smoke.mjs && node tests/package/packed-smoke.mjs && node tests/package/bundle-smoke.mjs",
|
|
42
|
+
"check": "pnpm run typecheck && pnpm run lint && pnpm run test",
|
|
43
|
+
"audit": "pnpm audit --audit-level=moderate",
|
|
44
|
+
"release:check": "pnpm run check && pnpm run test:browser && pnpm run test:browser:firefox && pnpm run test:browser:webkit && pnpm run test:package && pnpm run audit && git diff --check",
|
|
26
45
|
"prepare": "npm run build"
|
|
27
46
|
},
|
|
28
47
|
"keywords": [
|
|
@@ -45,28 +64,40 @@
|
|
|
45
64
|
"homepage": "https://gsap-react-marquee-docs.vercel.app/",
|
|
46
65
|
"peerDependencies": {
|
|
47
66
|
"@gsap/react": "^2.1.1",
|
|
48
|
-
"clsx": "^2.1.0",
|
|
49
67
|
"gsap": "^3.12.0",
|
|
50
|
-
"react": ">=18"
|
|
51
|
-
"react-dom": ">=18",
|
|
52
|
-
"tailwind-merge": "^2.2.0"
|
|
68
|
+
"react": ">=18"
|
|
53
69
|
},
|
|
54
70
|
"devDependencies": {
|
|
71
|
+
"@eslint/js": "^9.39.5",
|
|
55
72
|
"@gsap/react": "^2.1.1",
|
|
56
|
-
"@
|
|
57
|
-
"@rollup/plugin-
|
|
58
|
-
"@rollup/plugin-
|
|
73
|
+
"@playwright/test": "^1.61.1",
|
|
74
|
+
"@rollup/plugin-commonjs": "^29.0.3",
|
|
75
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
76
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
77
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
78
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
79
|
+
"@testing-library/react": "^16.3.2",
|
|
80
|
+
"@types/node": "^22.20.1",
|
|
59
81
|
"@types/react": "^18.3.3",
|
|
60
82
|
"@types/react-dom": "^18.3.0",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
83
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
84
|
+
"eslint": "^9.39.5",
|
|
85
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
86
|
+
"globals": "^16.5.0",
|
|
87
|
+
"jsdom": "^29.1.1",
|
|
88
|
+
"lightningcss": "^1.32.0",
|
|
89
|
+
"react": "^19.2.7",
|
|
90
|
+
"react-dom": "^19.2.7",
|
|
91
|
+
"rollup": "^4.62.2",
|
|
92
|
+
"rollup-plugin-dts": "^6.4.1",
|
|
66
93
|
"tslib": "^2.8.1",
|
|
67
|
-
"typescript": "^5.5.2"
|
|
94
|
+
"typescript": "^5.5.2",
|
|
95
|
+
"typescript-eslint": "^8.64.0",
|
|
96
|
+
"vite": "^8.1.5",
|
|
97
|
+
"vitest": "^4.1.10"
|
|
68
98
|
},
|
|
69
99
|
"dependencies": {
|
|
70
|
-
"
|
|
100
|
+
"clsx": "^2.1.1",
|
|
101
|
+
"tailwind-merge": "^2.3.0"
|
|
71
102
|
}
|
|
72
103
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.gsap-react-marquee-container:after{background:linear-gradient(270deg,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);left:0}.gsap-react-marquee-container:after,.gsap-react-marquee-container:before{content:"";height:100%;pointer-events:none;position:absolute;top:0;width:15%;z-index:10}.gsap-react-marquee-container:before{background:linear-gradient(90deg,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);right:0}.gsap-react-marquee{flex:1;height:max-content;width:auto}.gsap-react-marquee,.gsap-react-marquee-content{display:flex;line-height:100%;white-space:preserve nowrap}.gsap-react-marquee-content{overflow:hidden;width:max-content}
|
package/dist/index.cjs.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t,r=require("react"),n=require("@gsap/react"),a=require("gsap"),o=require("gsap/all"),i=require("clsx"),s=require("tailwind-merge"),l={exports:{}},c={},u={};"production"===process.env.NODE_ENV?l.exports=function(){if(e)return c;e=1;var t=Symbol.for("react.transitional.element"),r=Symbol.for("react.fragment");function n(e,r,n){var a=null;if(void 0!==n&&(a=""+n),void 0!==r.key&&(a=""+r.key),"key"in r)for(var o in n={},r)"key"!==o&&(n[o]=r[o]);else n=r;return r=n.ref,{$$typeof:t,type:e,key:a,ref:void 0!==r?r:null,props:n}}return c.Fragment=r,c.jsx=n,c.jsxs=n,c}():l.exports=(t||(t=1,"production"!==process.env.NODE_ENV&&function(){function e(t){if(null==t)return null;if("function"==typeof t)return t.$$typeof===q?null:t.displayName||t.name||null;if("string"==typeof t)return t;switch(t){case m:return"Fragment";case y:return"Profiler";case g:return"StrictMode";case w:return"Suspense";case S:return"SuspenseList";case _:return"Activity"}if("object"==typeof t)switch(t.tag,t.$$typeof){case p:return"Portal";case h:return(t.displayName||"Context")+".Provider";case v:return(t._context.displayName||"Context")+".Consumer";case b:var r=t.render;return(t=t.displayName)||(t=""!==(t=r.displayName||r.name||"")?"ForwardRef("+t+")":"ForwardRef"),t;case x:return null!==(r=t.displayName||null)?r:e(t.type)||"Memo";case k:r=t._payload,t=t._init;try{return e(t(r))}catch(e){}}return null}function t(e){return""+e}function n(e){try{t(e);var r=!1}catch(e){r=!0}if(r){var n=(r=console).error,a="function"==typeof Symbol&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return n.call(r,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",a),t(e)}}function a(t){if(t===m)return"<>";if("object"==typeof t&&null!==t&&t.$$typeof===k)return"<...>";try{var r=e(t);return r?"<"+r+">":"<...>"}catch(e){return"<...>"}}function o(){return Error("react-stack-top-frame")}function i(){var t=e(this.type);return N[t]||(N[t]=!0),void 0!==(t=this.props.ref)?t:null}function s(t,r,a,o,s,u,f,p){var m,g=r.children;if(void 0!==g)if(o){if(j(g)){for(o=0;o<g.length;o++)l(g[o]);Object.freeze&&Object.freeze(g)}}else l(g);if(E.call(r,"key")){g=e(t);var y=Object.keys(r).filter(function(e){return"key"!==e});o=0<y.length?"{key: someKey, "+y.join(": ..., ")+": ...}":"{key: someKey}",A[g+o]||(y=0<y.length?"{"+y.join(": ..., ")+": ...}":"{}",A[g+o]=!0)}if(g=null,void 0!==a&&(n(a),g=""+a),function(e){if(E.call(e,"key")){var t=Object.getOwnPropertyDescriptor(e,"key").get;if(t&&t.isReactWarning)return!1}return void 0!==e.key}(r)&&(n(r.key),g=""+r.key),"key"in r)for(var v in a={},r)"key"!==v&&(a[v]=r[v]);else a=r;return g&&function(e){function t(){c||(c=!0)}t.isReactWarning=!0,Object.defineProperty(e,"key",{get:t,configurable:!0})}(a,"function"==typeof t&&(t.displayName||t.name)),function(e,t,r,n,a,o,s,l){return r=o.ref,e={$$typeof:d,type:e,key:t,props:o,_owner:a},null!==(void 0!==r?r:null)?Object.defineProperty(e,"ref",{enumerable:!1,get:i}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:s}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:l}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}(t,g,u,0,null===(m=O.A)?null:m.getOwner(),a,f,p)}function l(e){"object"==typeof e&&null!==e&&e.$$typeof===d&&e._store&&(e._store.validated=1)}var c,f=r,d=Symbol.for("react.transitional.element"),p=Symbol.for("react.portal"),m=Symbol.for("react.fragment"),g=Symbol.for("react.strict_mode"),y=Symbol.for("react.profiler"),v=Symbol.for("react.consumer"),h=Symbol.for("react.context"),b=Symbol.for("react.forward_ref"),w=Symbol.for("react.suspense"),S=Symbol.for("react.suspense_list"),x=Symbol.for("react.memo"),k=Symbol.for("react.lazy"),_=Symbol.for("react.activity"),q=Symbol.for("react.client.reference"),O=f.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,E=Object.prototype.hasOwnProperty,j=Array.isArray,T=console.createTask?console.createTask:function(){return null},N={},C=(f={react_stack_bottom_frame:function(e){return e()}}).react_stack_bottom_frame.bind(f,o)(),P=T(a(o)),A={};u.Fragment=m,u.jsx=function(e,t,r,n,o){var i=1e4>O.recentlyCreatedOwnerStacks++;return s(e,t,r,!1,0,o,i?Error("react-stack-top-frame"):C,i?T(a(e)):P)},u.jsxs=function(e,t,r,n,o){var i=1e4>O.recentlyCreatedOwnerStacks++;return s(e,t,r,!0,0,o,i?Error("react-stack-top-frame"):C,i?T(a(e)):P)}}()),u);var f=l.exports;!function(e,t){void 0===t&&(t={});var r=t.insertAt;if("undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],a=document.createElement("style");a.type="text/css","top"===r&&n.firstChild?n.insertBefore(a,n.firstChild):n.appendChild(a),a.styleSheet?a.styleSheet.cssText=e:a.appendChild(document.createTextNode(e))}}('.gsap-react-marquee-container{display:flex;overflow:hidden;position:relative;white-space:preserve nowrap;width:100%}.gsap-react-marquee-container:after{background:linear-gradient(270deg,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);left:0}.gsap-react-marquee-container:after,.gsap-react-marquee-container:before{content:"";height:100%;pointer-events:none;position:absolute;top:0;width:15%;z-index:10}.gsap-react-marquee-container:before{background:linear-gradient(90deg,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);right:0}.gsap-react-marquee-vertical:after{background:linear-gradient(180deg,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);height:50%;left:0;top:60%;width:100%}.gsap-react-marquee-vertical:before{background:linear-gradient(1turn,hsla(0,0%,100%,0) 0,var(--gradient-color) 75%);height:50%;left:0;right:auto;top:0;width:100%}.gsap-react-marquee{flex:1;height:max-content;width:auto}.gsap-react-marquee,.gsap-react-marquee-content{display:flex;line-height:100%;white-space:preserve nowrap}.gsap-react-marquee-content{overflow:hidden;width:max-content}');const d=(...e)=>s.twMerge(i.clsx(e)),p=e=>{let t=e;for(;t;){const e=window.getComputedStyle(t).backgroundColor;if(e&&"rgba(0, 0, 0, 0)"!==e&&"transparent"!==e)return e;t=t.parentElement}return"transparent"},m=(e,t,r,n,o)=>{const{spacing:i=16}=o;a.set(e,{gap:`${i}px`,flexDirection:n?"column":"row"}),a.set(t,{gap:`${i}px`}),n&&a.set(r,{overflow:"visible"})},g=e=>{if(e.style.width&&"auto"!==e.style.width)return!0;const t=window.getComputedStyle(e).width;if("auto"===t||!t)return!1;const r=e.parentElement;if(r){const e=window.getComputedStyle(r);if("100%"===t&&"auto"===e.width)return!1}return!0},y=(e,t,r)=>r.fill&&e<t?Math.ceil(t/e):1,v=(e,t,r)=>{const{fill:n=!1}=r;return n?"auto":e<t?"100%":e>t?`${e}px`:`${t}px`},h=(e,t,r,n,i,s,l)=>{const{spacing:c=16,speed:u=100,delay:f=0,paused:d=!1}=l,p=[],m=[],g=e.length-1,y=s?"yPercent":"xPercent",v=s?"y":"x",h=s?"height":"width";a.set(e,{[y]:(e,t)=>{const r=p[e]=parseFloat(String(a.getProperty(t,h,"px")));return m[e]=parseFloat(String(a.getProperty(t,v,"px")))/r*100+Number(a.getProperty(t,y)),m[e]}}),a.set(e,{[v]:0});const b=e[g],w=s?b.offsetTop:b.offsetLeft,S=s?b.offsetHeight:b.offsetWidth,x=w+m[g]/100*p[g]-t+S+c;if(e.forEach((e,n)=>{const a=m[n]/100*p[n],o=(s?e.offsetTop:e.offsetLeft)+a-t+p[n];r.to(e,{[y]:(a-o)/p[n]*100,duration:o/u},0).fromTo(e,{[y]:(a-o+x)/p[n]*100},{[y]:m[n],duration:(a-o+x-a)/u,immediateRender:!1},o/u)}),r.delay(f),n){if(d)return void r.pause();r.progress(1).pause(),a.delayedCall(f,()=>{r.reverse(),r.eventCallback("onReverseComplete",()=>{r.totalTime(r.rawTime()+100*r.duration())})})}let k;if("function"==typeof o.Draggable&&l.draggable){k=document.createElement("div");const e=a.utils.wrap(0,1);let t,l;const c=()=>{const n=s?u.startY-u.y:u.startX-u.x;r.progress(e(l+n*t))};o.InertiaPlugin;const u=o.Draggable.create(k,{trigger:i,type:s?"y":"x",onPress(){a.killTweensOf(r),r.pause(),l=r.progress(),t=1/x,a.set(k,{[v]:l/-t})},onDrag:c,onThrowUpdate:c,overshootTolerance:0,inertia:!0,onThrowComplete:()=>{if(n){if(d)return void r.pause();r.progress(r.progress()).pause(),a.delayedCall(f,()=>{r.reverse(),r.eventCallback("onReverseComplete",()=>{r.totalTime(r.rawTime()+100*r.duration())})})}else r.play()}})[0]}};a.registerPlugin(n.useGSAP,o.Observer,o.InertiaPlugin,o.Draggable);const b=r.forwardRef((e,t)=>{const{children:i,className:s,dir:l="left",loop:c=-1,paused:u=!1,fill:g=!1,scrollFollow:b=!1,scrollSpeed:w=2.5,gradient:S=!1,gradientColor:x=null,pauseOnHover:k=!1,spacing:_=16,speed:q=100}=e,O=r.useRef(null),E=t||O,j=r.useRef(null),[T,N]=r.useState(1),[C,P]=r.useState(null),[A,R]=r.useState(0);r.useLayoutEffect(()=>{if(!S||!(null==E?void 0:E.current))return;const e=p(E.current);P(e)},[S]);const L="up"===l||"down"===l,$="down"===l||"right"===l;r.useLayoutEffect(()=>{const e=E.current;if(!e)return;const t=e.querySelector(".gsap-react-marquee .gsap-react-marquee-content");let r=null;const n=()=>{null==r&&(r=requestAnimationFrame(()=>{R(e=>e+1),r=null}))},a=t?new ResizeObserver(n):null;t&&a&&a.observe(t);const o=Array.from(e.querySelectorAll("img")),i=()=>n();return o.forEach(e=>{e.complete||(e.addEventListener("load",i),e.addEventListener("error",i))}),()=>{null==a||a.disconnect(),o.forEach(e=>{e.removeEventListener("load",i),e.removeEventListener("error",i)}),null!=r&&cancelAnimationFrame(r)}},[i]),n.useGSAP((t,r)=>{if(!(null==j?void 0:j.current)||!E.current||!r)return;const n=null==E?void 0:E.current,i=a.utils.toArray(n.querySelectorAll(".gsap-react-marquee")),s=a.utils.toArray(n.querySelectorAll(".gsap-react-marquee .gsap-react-marquee-content"));if(!j.current||!s)return;const l=a.timeline({paused:u,repeat:c,defaults:{ease:"none"},onReverseComplete(){l.totalTime(l.rawTime()+100*l.duration())}});m(n,i,s,L,e);const f=L?n.offsetHeight:n.offsetWidth,d=L?s[0].offsetHeight:s[0].offsetWidth,p=L?s[0].offsetTop:s[0].offsetLeft;let S=null;const x=Math.min(4,Math.max(1.1,w));N(y(d,f,e));const _=a.utils.toArray(i).map(e=>L?e.offsetHeight:e.offsetWidth).reduce((e,t)=>e+t,0),q=v(_/(1===T?2:T),f,e);a.set(i,{[L?"minHeight":"minWidth"]:q,flex:g?"0 0 auto":"1"}),h(g?s:i,p,l,$,i,L,e),b&&(S=o.Observer.create({onChangeY(e){let t=x*($?-1:1);e.deltaY<0&&(t*=-1),a.timeline({defaults:{ease:"none"}}).to(l,{timeScale:t*x,duration:.2,overwrite:!0}).to(l,{timeScale:t/x,duration:1},"+=0.3")}}));const O=r(()=>{l.pause()}),C=r(()=>{$?l.reverse():l.play()});return k&&(n.addEventListener("mouseenter",O),n.addEventListener("mouseleave",C)),()=>{n.removeEventListener("mouseenter",O),n.removeEventListener("mouseleave",C),l.kill(),null==S||S.kill()}},{dependencies:[T,l,c,u,g,b,w,S,x,k,_,q,i,A],revertOnUpdate:!0});const W=r.useMemo(()=>!Number.isFinite(T)||T<=0?null:Array.from({length:T},(e,t)=>f.jsx("div",{className:d("gsap-react-marquee"),children:f.jsx("div",{className:d("gsap-react-marquee-content",s),children:i})},t)),[T,s,i]);return f.jsxs("div",{ref:E,style:{"--gradient-color":x||(S&&C?C:"transparent")},className:d("gsap-react-marquee-container",{"gsap-react-marquee-vertical":L}),children:[f.jsx("div",{ref:j,className:d("gsap-react-marquee"),children:f.jsx("div",{className:d("gsap-react-marquee-content",s),children:i})}),W]})});b.displayName="GSAPReactMarquee",exports.calculateDuplicates=y,exports.cn=d,exports.coreAnimation=h,exports.default=b,exports.getEffectiveBackgroundColor=p,exports.getMinWidth=v,exports.getTargetWidth=(e,t)=>g(e)?t?e.offsetHeight:e.offsetWidth:t?window.innerHeight:window.innerWidth,exports.hasDefinedWidth=g,exports.setupContainerStyles=m;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import "./gsap-react-marquee.style.css";
|
|
2
|
-
import type { GSAPReactMarqueeProps } from "./gsap-react-marquee.type";
|
|
3
|
-
declare const GSAPReactMarquee: import("react").ForwardRefExoticComponent<GSAPReactMarqueeProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
4
|
-
export default GSAPReactMarquee;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export declare const marqueeStyles = "\n.gsap-react-marquee-container::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n width: 15%;\n height: 100%;\n background: linear-gradient(\n 270deg,\n rgba(255, 255, 255, 0) 0%,\n var(--gradient-color) 75%\n );\n z-index: 10;\n pointer-events: none;\n}\n\n.gsap-react-marquee-container::before {\n content: \"\";\n position: absolute;\n top: 0;\n right: 0;\n width: 15%;\n height: 100%;\n background: linear-gradient(\n 90deg,\n rgba(255, 255, 255, 0) 0%,\n var(--gradient-color) 75%\n );\n z-index: 10;\n pointer-events: none;\n}\n\n.gsap-react-marquee {\n width: auto;\n height: max-content;\n line-height: 100%;\n white-space: preserve nowrap;\n display: flex;\n flex: 1;\n}\n\n.gsap-react-marquee-content {\n width: max-content;\n line-height: 100%;\n white-space: preserve nowrap;\n display: flex;\n overflow: hidden;\n}\n";
|
|
2
|
-
export declare const injectStyles: () => void;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
export type GSAPReactMarqueeProps = {
|
|
3
|
-
children: ReactNode;
|
|
4
|
-
className?: string;
|
|
5
|
-
dir?: "right" | "left" | "up" | "down";
|
|
6
|
-
loop?: number;
|
|
7
|
-
paused?: boolean;
|
|
8
|
-
delay?: number;
|
|
9
|
-
speed?: number;
|
|
10
|
-
fill?: boolean;
|
|
11
|
-
pauseOnHover?: boolean;
|
|
12
|
-
gradient?: boolean;
|
|
13
|
-
gradientColor?: string;
|
|
14
|
-
spacing?: number;
|
|
15
|
-
draggable?: boolean;
|
|
16
|
-
scrollFollow?: boolean;
|
|
17
|
-
scrollSpeed?: number;
|
|
18
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type ClassValue } from "clsx";
|
|
2
|
-
import type { GSAPReactMarqueeProps } from "./gsap-react-marquee.type";
|
|
3
|
-
export declare const cn: (...inputs: ClassValue[]) => string;
|
|
4
|
-
export declare const getEffectiveBackgroundColor: (el: HTMLElement) => string;
|
|
5
|
-
export declare const setupContainerStyles: (containerMarquee: HTMLElement, marquees: HTMLElement[], marqueesChildren: HTMLElement[], isVertical: boolean, props: GSAPReactMarqueeProps) => void;
|
|
6
|
-
export declare const hasDefinedWidth: (element: HTMLElement) => boolean;
|
|
7
|
-
export declare const getTargetWidth: (containerElement: HTMLElement, isVertical: boolean) => number;
|
|
8
|
-
export declare const calculateDuplicates: (marqueeChildrenWidth: number, containerMarqueeWidth: number, props: GSAPReactMarqueeProps) => number;
|
|
9
|
-
export declare const getMinWidth: (totalSize: number, containerSize: number, props: GSAPReactMarqueeProps) => string | number;
|
|
10
|
-
export declare const coreAnimation: (elementsToAnimate: HTMLElement[], startPos: number, tl: gsap.core.Timeline, isReverse: boolean, draggableTrigger: HTMLElement | HTMLElement[], isVertical: boolean, props: GSAPReactMarqueeProps) => void;
|
package/dist/types/index.d.ts
DELETED