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 CHANGED
@@ -1,150 +1,434 @@
1
1
  # GSAP React Marquee
2
2
 
3
- A high-performance React marquee component powered by GSAP animations with intelligent container detection and seamless looping.
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
- ## Usage
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} fill={true} spacing={16}>
23
- <div>Hello world</div>
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
- ## Features
46
+ The package injects its base CSS through the bundled entrypoint, so no separate stylesheet import is required.
30
47
 
31
- - **Intelligent Container Detection**: Automatically detects whether containers have fixed dimensions or adapt to content, preventing recursive expansion loops
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
- ## Props
50
+ ### Continuous Fill
41
51
 
42
- | Prop | Type | Default | Description |
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
- ## Advanced Features
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
- ### Intelligent Container Detection
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
- The component automatically detects container dimensions to prevent recursive expansion:
66
+ ### Vertical Marquee
65
67
 
66
- - **Fixed Width Containers**: Uses container dimensions for optimal duplicate calculation
67
- - **Auto-Width Containers**: Falls back to viewport dimensions to prevent layout loops
68
- - **Safety Limits**: Maximum of 15 duplicates to prevent performance issues
69
- - **Development Logging**: Debug information in development mode
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
- ### Seamless Looping Algorithm
85
+ ### Gradient Overlay
72
86
 
73
- Enhanced duplicate calculation ensures perfect infinite scrolling:
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
- - **Smart Target Size**: Intelligently determines the space to fill (width for horizontal, height for vertical)
76
- - **Gap Prevention**: Calculates exact duplicates needed to eliminate visual gaps
77
- - **Performance Optimized**: Minimal DOM elements for maximum performance
89
+ ```tsx
90
+ <Marquee gradient gradientColor="#ffffff">
91
+ <span>Faded edges</span>
92
+ </Marquee>
93
+ ```
78
94
 
79
- ### Orientation-Aware Animations
95
+ ### Pause On Hover
80
96
 
81
- The marquee automatically adapts its animation system based on direction:
97
+ ```tsx
98
+ <Marquee pauseOnHover>
99
+ <span>Hover to pause</span>
100
+ </Marquee>
101
+ ```
82
102
 
83
- - **Horizontal (`left`/`right`)**: Uses `xPercent` for X-axis animations with `flexDirection: row`
84
- - **Vertical (`up`/`down`)**: Uses `yPercent` for Y-axis animations with `flexDirection: column`
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
- ### Interactive Features
106
+ ### Controlled Pause
90
107
 
91
- - **Draggable Support**: Drag to manually control the marquee position
92
- - **Scroll Synchronization**: Link marquee speed to page scroll direction
93
- - **Pause on Hover**: Temporarily stop animation when hovering
94
- - **Inertia Scrolling**: Smooth momentum-based scrolling when dragging (requires GSAP InertiaPlugin)
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
- ## Examples
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
- ### Basic Marquee
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 dir="left" speed={50}>
102
- <span>Scrolling text goes here</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
- ### Vertical Marquee with Gradient
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 dir="up" speed={80} gradient={true} gradient>
110
- <div>Item 1</div>
111
- <div>Item 2</div>
112
- <div>Item 3</div>
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
- ### Interactive Draggable Marquee
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 dir="right" speed={100} draggable={true} pauseOnHover={true}>
120
- <img src="image1.jpg" alt="1" />
121
- <img src="image2.jpg" alt="2" />
122
- <img src="image3.jpg" alt="3" />
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
- ### Scroll-Synced Marquee
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 dir="left" speed={120} scrollFollow={true} scrollSpeed={3}>
130
- <div>Scroll the page to see the effect</div>
307
+ <Marquee className="items-center gap-4">
308
+ <span>One</span>
309
+ <span>Two</span>
131
310
  </Marquee>
132
311
  ```
133
312
 
134
- ## Changelog
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
- ### v0.3.0
421
+ ```bash
422
+ pnpm run test:package
423
+ ```
137
424
 
138
- - 🎨 **BREAKING**: Removed `alignVertical` prop - vertical marquees now use native flex-column layout
139
- - ✨ Added proper Y-axis animations for vertical directions (`up`/`down`)
140
- - 🎨 Smart gradient overlays that auto-adapt to marquee orientation
141
- - 🔧 Refactored animation engine to support both X and Y axis seamlessly
142
- - 📊 Improved dimension calculations for vertical marquees
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
- ### v0.2.4
432
+ ## License
145
433
 
146
- - ✨ Added intelligent container detection to prevent recursive expansion
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: (el: HTMLElement) => string;
27
- declare const setupContainerStyles: (containerMarquee: HTMLElement, marquees: HTMLElement[], marqueesChildren: HTMLElement[], isVertical: boolean, props: GSAPReactMarqueeProps) => void;
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
- declare const calculateDuplicates: (marqueeChildrenWidth: number, containerMarqueeWidth: number, props: GSAPReactMarqueeProps) => number;
31
- declare const getMinWidth: (totalSize: number, containerSize: number, props: GSAPReactMarqueeProps) => string | number;
32
- declare const coreAnimation: (elementsToAnimate: HTMLElement[], startPos: number, tl: gsap.core.Timeline, isReverse: boolean, draggableTrigger: HTMLElement | HTMLElement[], isVertical: boolean, props: GSAPReactMarqueeProps) => void;
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.3.1",
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.js",
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.js",
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
- "build": "rollup -c",
25
- "build:watch": "rollup -c -w",
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
- "@rollup/plugin-commonjs": "^25.0.7",
57
- "@rollup/plugin-node-resolve": "^15.2.3",
58
- "@rollup/plugin-typescript": "^11.1.6",
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
- "clsx": "^2.1.1",
62
- "rollup": "^4.18.0",
63
- "rollup-plugin-dts": "^6.1.1",
64
- "rollup-plugin-postcss": "^4.0.2",
65
- "tailwind-merge": "^2.3.0",
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
- "@rollup/plugin-terser": "^0.4.4"
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;
@@ -1,3 +0,0 @@
1
- export { default } from "./components/gsap-react-marquee";
2
- export type { GSAPReactMarqueeProps } from "./components/gsap-react-marquee.type";
3
- export * from "./components/gsap-reactmarquee.utils";