rune-scroller 1.0.0 → 2.1.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,4 +1,12 @@
1
- # ⚡ Rune Scroller
1
+ # ⚡ Rune Scroller - Full Reference
2
+
3
+ **📚 Complete API Reference** — Detailed documentation for all features and options.
4
+
5
+ **Quick start?** See [README.md](../README.md) for a simpler introduction.
6
+
7
+ **Development?** See [CLAUDE.md](../CLAUDE.md) for the developer guide.
8
+
9
+ ---
2
10
 
3
11
  <div align="center">
4
12
  <img src="./logo.png" alt="Rune Scroller Logo" width="200" />
@@ -9,17 +17,63 @@
9
17
  > 🚀 **Open Source** by [ludoloops](https://github.com/ludoloops) at [LeLab.dev](https://lelab.dev)
10
18
  > 📜 Licensed under **MIT**
11
19
 
20
+ <div align="center">
21
+ <a href="https://bundlephobia.com/package/rune-scroller">
22
+ <img src="https://img.shields.io/bundlephobia/minzip/rune-scroller" alt="minzipped size" />
23
+ </a>
24
+ <a href="https://bundlephobia.com/package/rune-scroller">
25
+ <img src="https://img.shields.io/bundlephobia/min/rune-scroller" alt="minified size" />
26
+ </a>
27
+ </div>
28
+
12
29
  ---
13
30
 
14
31
  ## ✨ Features
15
32
 
16
- - **~2KB gzipped** - Minimal overhead
33
+ - **12.7KB gzipped** (40.3KB uncompressed) - Minimal overhead, optimized for production
17
34
  - **Zero dependencies** - Pure Svelte 5 + IntersectionObserver
18
35
  - **14 animations** - Fade, Zoom, Flip, Slide, Bounce variants
19
- - **TypeScript** - Full type coverage
36
+ - **Full TypeScript support** - Type definitions generated from JSDoc
20
37
  - **SSR-ready** - SvelteKit compatible
21
38
  - **GPU-accelerated** - Pure CSS transforms
22
39
  - **Accessible** - Respects `prefers-reduced-motion`
40
+ - **v2.0.0 New** - `onVisible` callback, ResizeObserver support, animation validation, sentinel customization
41
+ - **✨ Latest** - `useIntersection` migrated to Svelte 5 `$effect` rune for better lifecycle management
42
+ - **🚀 Bundle optimized** - CSS with custom properties, production build minification
43
+
44
+ ---
45
+
46
+ ## 📊 Performance & Quality
47
+
48
+ **Bundle Optimization (2026-01-12):**
49
+ - ✅ **121/121 tests passing** (100%)
50
+ - ✅ **Bundle size:** 12.7KB gzipped (-1.6KB from v2.0.0, -11.2%)
51
+ - ✅ **Unpacked size:** 40.3KB (-7.1KB from v2.0.0, -15.0%)
52
+ - ✅ **Type safety:** 0 errors (JSDoc + TypeScript)
53
+ - ✅ **Memory leaks:** 0 detected
54
+ - ✅ **Svelte 5 aligned:** Full runes support
55
+ - ✅ **Removed deprecated `animate` action** - Simplified API
56
+ - ✅ **Tests optimized for npm** - Excluded from package (tests/ directory)
57
+ - ✅ **CSS optimized:** CSS custom properties (-36% CSS reduction)
58
+ - ✅ **Production ready:** NODE_ENV guards for console warnings
59
+
60
+ **Bundle breakdown (unpacked, optimized):**
61
+ - `runeScroller.js`: 5.0KB (-12%)
62
+ - `dom-utils.js`: 3.4KB (-24%)
63
+ - `animations.css`: 2.5KB (-36%) ← Optimized with CSS custom properties
64
+ - `types.js`: 2.1KB
65
+ - `useIntersection.svelte.js`: 2.2KB (-18%)
66
+ - `observer-utils.js`: 773B (-52%)
67
+ - Type definitions (.d.ts): ~6KB (-33%)
68
+ - Other (index.js, LICENSE, package.json): 2.8KB
69
+
70
+ **Optimization details:**
71
+ - CSS custom properties eliminate repetitive rules
72
+ - Production builds strip console warnings via NODE_ENV
73
+ - JSDoc optimized (kept types, removed verbose descriptions)
74
+ - All features and animations remain unchanged
75
+
76
+ See [`MIGRATION_METRICS.md`](../MIGRATION_METRICS.md) for detailed performance benchmarks.
23
77
 
24
78
  ---
25
79
 
@@ -91,17 +145,17 @@ yarn add rune-scroller
91
145
 
92
146
  ### Fade (5)
93
147
  - `fade-in` - Simple opacity fade
94
- - `fade-in-up` - Fade + move up 100px
95
- - `fade-in-down` - Fade + move down 100px
96
- - `fade-in-left` - Fade + move from right
97
- - `fade-in-right` - Fade + move from left
148
+ - `fade-in-up` - Fade + move up 300px
149
+ - `fade-in-down` - Fade + move down 300px
150
+ - `fade-in-left` - Fade + move from right 300px
151
+ - `fade-in-right` - Fade + move from left 300px
98
152
 
99
153
  ### Zoom (5)
100
- - `zoom-in` - Scale from 0.6 to 1
101
- - `zoom-out` - Scale from 1.2 to 1
102
- - `zoom-in-up` - Zoom + move up
103
- - `zoom-in-left` - Zoom + move from right
104
- - `zoom-in-right` - Zoom + move from left
154
+ - `zoom-in` - Scale from 0.3 to 1
155
+ - `zoom-out` - Scale from 2 to 1
156
+ - `zoom-in-up` - Zoom (0.5→1) + move up 300px
157
+ - `zoom-in-left` - Zoom (0.5→1) + move from right 300px
158
+ - `zoom-in-right` - Zoom (0.5→1) + move from left 300px
105
159
 
106
160
  ### Others (4)
107
161
  - `flip` - 3D flip on Y-axis
@@ -116,20 +170,26 @@ yarn add rune-scroller
116
170
  ```typescript
117
171
  interface RuneScrollerOptions {
118
172
  animation?: AnimationType; // Animation name (default: 'fade-in')
119
- duration?: number; // Duration in ms (default: 2000)
173
+ duration?: number; // Duration in ms (default: 800)
120
174
  repeat?: boolean; // Repeat on scroll (default: false)
121
175
  debug?: boolean; // Show sentinel as visible line (default: false)
122
176
  offset?: number; // Sentinel offset in px (default: 0, negative = above)
177
+ onVisible?: (element: HTMLElement) => void; // Callback when animation triggers (v2.0.0+)
178
+ sentinelColor?: string; // Sentinel debug color, e.g. '#ff6b6b' (v2.0.0+)
179
+ sentinelId?: string; // Custom ID for sentinel identification (v2.0.0+)
123
180
  }
124
181
  ```
125
182
 
126
183
  ### Option Details
127
184
 
128
- - **`animation`** - Type of animation to play. Choose from 14 built-in animations listed above.
129
- - **`duration`** - How long the animation lasts in milliseconds (default: 2000ms).
185
+ - **`animation`** - Type of animation to play. Choose from 14 built-in animations listed above. Invalid animations automatically fallback to 'fade-in' with a console warning.
186
+ - **`duration`** - How long the animation lasts in milliseconds (default: 800ms).
130
187
  - **`repeat`** - If `true`, animation plays every time sentinel enters viewport. If `false`, plays only once.
131
- - **`debug`** - If `true`, displays the sentinel element as a visible cyan line below your element. Useful for seeing exactly when animations trigger.
188
+ - **`debug`** - If `true`, displays the sentinel element as a visible line below your element. Useful for seeing exactly when animations trigger. Default color is cyan (#00e0ff), customize with `sentinelColor`.
132
189
  - **`offset`** - Offset of the sentinel in pixels. Positive values move sentinel down (delays animation), negative values move it up (triggers earlier). Useful for large elements where you want animation to trigger before the entire element is visible.
190
+ - **`onVisible`** *(v2.0.0+)* - Callback function triggered when the animation becomes visible. Receives the animated element as parameter. Useful for analytics, lazy loading, or triggering custom effects.
191
+ - **`sentinelColor`** *(v2.0.0+)* - Customize the debug sentinel color (e.g., '#ff6b6b' for red). Only visible when `debug: true`. Useful for distinguishing multiple sentinels on the same page.
192
+ - **`sentinelId`** *(v2.0.0+)* - Set a custom ID for the sentinel element. If not provided, an auto-ID is generated (`sentinel-1`, `sentinel-2`, etc.). Useful for identifying sentinels in DevTools and tracking which element owns which sentinel.
133
193
 
134
194
  ### Examples
135
195
 
@@ -179,40 +239,50 @@ interface RuneScrollerOptions {
179
239
  }}>
180
240
  Content with delayed animation
181
241
  </div>
182
- ```
183
242
 
184
- ---
185
-
186
- ## 🔧 Advanced Usage
187
-
188
- ### Using the `animate` Action (Direct Control)
243
+ <!-- v2.0.0: onVisible callback for analytics tracking -->
244
+ <div use:runeScroller={{
245
+ animation: 'fade-in-up',
246
+ onVisible: (el) => {
247
+ console.log('Animation visible!', el);
248
+ // Track analytics, load images, trigger API calls, etc.
249
+ window.gtag?.('event', 'animation_visible', { element: el.id });
250
+ }
251
+ }}>
252
+ Tracked animation
253
+ </div>
189
254
 
190
- For advanced use cases, use `animate` for fine-grained IntersectionObserver control:
255
+ <!-- v2.0.0: Custom sentinel color for debugging -->
256
+ <div use:runeScroller={{
257
+ animation: 'fade-in',
258
+ debug: true,
259
+ sentinelColor: '#ff6b6b' // Red instead of default cyan
260
+ }}>
261
+ Red debug sentinel
262
+ </div>
191
263
 
192
- ```svelte
193
- <script>
194
- import { animate } from 'rune-scroller';
195
- import 'rune-scroller/animations.css';
196
- </script>
264
+ <!-- v2.0.0: Custom sentinel ID for identification -->
265
+ <div use:runeScroller={{
266
+ animation: 'zoom-in',
267
+ sentinelId: 'hero-zoom',
268
+ debug: true
269
+ }}>
270
+ Identified sentinel (shows "hero-zoom" in debug mode)
271
+ </div>
197
272
 
198
- <div use:animate={{
273
+ <!-- v2.0.0: Auto-ID (sentinel-1, sentinel-2, etc) -->
274
+ <div use:runeScroller={{
199
275
  animation: 'fade-in-up',
200
- duration: 1000,
201
- delay: 200,
202
- threshold: 0.5,
203
- offset: 20,
204
- once: true
276
+ debug: true
277
+ // sentinelId omitted → auto generates "sentinel-1", "sentinel-2", etc
205
278
  }}>
206
- Advanced control
279
+ Auto-identified sentinel
207
280
  </div>
208
281
  ```
209
282
 
210
- **Options:**
211
- - `threshold` - Intersection ratio to trigger (0-1)
212
- - `offset` - Viewport offset percentage (0-100)
213
- - `rootMargin` - Custom IntersectionObserver margin
214
- - `delay` - Animation delay in ms
215
- - `once` - Trigger only once
283
+ ---
284
+
285
+ ## 🔧 Advanced Usage
216
286
 
217
287
  ### Using Composables
218
288
 
@@ -245,11 +315,18 @@ Rune Scroller uses **sentinel-based triggering**:
245
315
  3. This ensures precise timing regardless of element size
246
316
  4. Uses native IntersectionObserver for performance
247
317
  5. Pure CSS animations (GPU-accelerated)
318
+ 6. *(v2.0.0)* Sentinel automatically repositions on element resize via ResizeObserver
248
319
 
249
320
  **Why sentinels?**
250
321
  - Accurate timing across all screen sizes
251
322
  - No complex offset calculations
252
323
  - Handles staggered animations naturally
324
+ - Sentinel stays fixed while element animates (no observer confusion with transforms)
325
+
326
+ **Automatic ResizeObserver** *(v2.0.0+)*
327
+ - Sentinel repositions automatically when element resizes
328
+ - Works with responsive layouts and dynamic content
329
+ - No configuration needed—it just works
253
330
 
254
331
  ---
255
332
 
@@ -304,16 +381,26 @@ Users who prefer reduced motion will see content without animations.
304
381
  ---
305
382
 
306
383
  ## 📚 API Reference
384
+ ### Public API
385
+
386
+ Rune Scroller exports a **single action-based API** (no components):
387
+
388
+ 1. **`runeScroller`** (default) - Sentinel-based, simple, powerful
389
+
390
+ **Why actions instead of components?**
391
+ - Actions are lightweight directives
392
+ - No DOM wrapper overhead
393
+ - Better performance
394
+ - More flexible
307
395
 
308
396
  ### Main Export
309
397
 
310
398
  ```typescript
311
- // Default export (recommended)
399
+ // Default export
312
400
  import runeScroller from 'rune-scroller';
313
401
 
314
402
  // Named exports
315
403
  import {
316
- animate, // Alternative action
317
404
  useIntersection, // Composable
318
405
  useIntersectionOnce, // Composable
319
406
  calculateRootMargin // Utility
@@ -323,7 +410,6 @@ import {
323
410
  import type {
324
411
  AnimationType,
325
412
  RuneScrollerOptions,
326
- AnimateOptions,
327
413
  IntersectionOptions,
328
414
  UseIntersectionReturn
329
415
  } from 'rune-scroller';
@@ -343,16 +429,9 @@ interface RuneScrollerOptions {
343
429
  repeat?: boolean;
344
430
  debug?: boolean;
345
431
  offset?: number;
346
- }
347
-
348
- interface AnimateOptions {
349
- animation?: AnimationType;
350
- duration?: number;
351
- delay?: number;
352
- threshold?: number;
353
- rootMargin?: string;
354
- offset?: number;
355
- once?: boolean;
432
+ onVisible?: (element: HTMLElement) => void; // v2.0.0+
433
+ sentinelColor?: string; // v2.0.0+
434
+ sentinelId?: string; // v2.0.0+
356
435
  }
357
436
  ```
358
437
 
@@ -406,7 +485,6 @@ interface AnimateOptions {
406
485
 
407
486
  - **npm Package**: [rune-scroller](https://www.npmjs.com/package/rune-scroller)
408
487
  - **GitHub**: [lelabdev/rune-scroller](https://github.com/lelabdev/rune-scroller)
409
- - **Documentation**: [CLAUDE.md](./CLAUDE.md)
410
488
  - **Changelog**: [CHANGELOG.md](./CHANGELOG.md)
411
489
 
412
490
  ---
@@ -423,10 +501,10 @@ Contributions welcome! Please open an issue or PR on GitHub.
423
501
 
424
502
  ```bash
425
503
  # Development
426
- pnpm install
427
- pnpm dev
428
- pnpm test
429
- pnpm build
504
+ bun install
505
+ bun run dev
506
+ bun test
507
+ bun run build
430
508
  ```
431
509
 
432
510
  ---
@@ -1,177 +1,80 @@
1
1
  /**
2
- * Reusable scroll animation styles
3
- * These styles define animation effects for scroll-triggered elements
2
+ * Reusable scroll animation styles - Optimized with CSS custom properties
3
+ * Reduces bundle size while maintaining all 14 animations
4
4
  */
5
5
 
6
- /* Base animation container */
6
+ /* Base animation container with optimized transitions */
7
7
  .scroll-animate,
8
8
  .animated-element {
9
9
  opacity: 0;
10
- transition: all var(--duration, 800ms) cubic-bezier(0.34, 1.56, 0.64, 1);
11
- transition-delay: var(--delay, 0ms);
10
+ transition: opacity var(--duration, 2500ms) linear var(--delay, 0ms),
11
+ transform var(--duration, 2500ms) cubic-bezier(0.34, 1.56, 0.64, 1) var(--delay, 0ms);
12
+ transform: perspective(1000px) translate(var(--tx, 0), var(--ty, 0)) scale(var(--scale, 1)) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) rotate(var(--rotate, 0deg));
12
13
  }
13
14
 
14
15
  .scroll-animate.is-visible,
15
16
  .animated-element.is-visible {
16
- opacity: 1;
17
- /* GPU acceleration only for visible elements to reduce initial memory pressure */
17
+ opacity: 1 !important;
18
18
  will-change: transform, opacity;
19
+ transform: perspective(1000px) translate(var(--tx, 0), var(--ty, 0)) scale(var(--scale, 1)) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) rotate(var(--rotate, 0deg));
19
20
  }
20
21
 
21
- /* Animation states - transform-specific initial states */
22
- /* (opacity: 0 is already set in .scroll-animate base class) */
22
+ /* Initial state - transform values before animation */
23
+ [data-animation='fade-in'] { --tx: 0; --ty: 0; }
24
+ [data-animation='fade-in-up'] { --ty: var(--translate-distance, 300px); }
25
+ [data-animation='fade-in-down'] { --ty: calc(-1 * var(--translate-distance, 300px)); }
26
+ [data-animation='fade-in-left'] { --tx: calc(-1 * var(--translate-distance, 300px)); }
27
+ [data-animation='fade-in-right'] { --tx: var(--translate-distance, 300px); }
23
28
 
24
- /* Fade In (no transform, just opacity) */
25
- [data-animation='fade-in'] {
26
- /* No transform needed, uses base opacity: 0 from .scroll-animate */
27
- }
28
-
29
- [data-animation='fade-in'].is-visible {
30
- /* Inherits opacity: 1 from .scroll-animate.is-visible */
31
- transform: none;
32
- }
33
-
34
- /* Fade In Up */
35
- [data-animation='fade-in-up'] {
36
- transform: translateY(300px);
37
- }
38
-
39
- [data-animation='fade-in-up'].is-visible {
40
- transform: translateY(0);
41
- }
42
-
43
- /* Fade In Down */
44
- [data-animation='fade-in-down'] {
45
- transform: translateY(-300px);
46
- }
47
-
48
- [data-animation='fade-in-down'].is-visible {
49
- transform: translateY(0);
50
- }
51
-
52
- /* Fade In Left */
53
- [data-animation='fade-in-left'] {
54
- transform: translateX(-300px);
55
- }
56
-
57
- [data-animation='fade-in-left'].is-visible {
58
- transform: translateX(0);
59
- }
60
-
61
- /* Fade In Right */
62
- [data-animation='fade-in-right'] {
63
- transform: translateX(300px);
64
- }
65
-
66
- [data-animation='fade-in-right'].is-visible {
67
- transform: translateX(0);
68
- }
69
-
70
- /* Zoom In */
71
- [data-animation='zoom-in'] {
72
- transform: scale(0.3);
73
- }
74
-
75
- [data-animation='zoom-in'].is-visible {
76
- transform: scale(1);
77
- }
78
-
79
- /* Zoom Out */
80
- [data-animation='zoom-out'] {
81
- transform: scale(2);
82
- }
83
-
84
- [data-animation='zoom-out'].is-visible {
85
- transform: scale(1);
86
- }
87
-
88
- /* Zoom In Up */
89
- [data-animation='zoom-in-up'] {
90
- transform: scale(0.5) translateY(300px);
91
- }
92
-
93
- [data-animation='zoom-in-up'].is-visible {
94
- transform: scale(1) translateY(0);
95
- }
96
-
97
- /* Zoom In Left */
98
- [data-animation='zoom-in-left'] {
99
- transform: scale(0.5) translateX(-300px);
100
- }
101
-
102
- [data-animation='zoom-in-left'].is-visible {
103
- transform: scale(1) translateX(0);
104
- }
105
-
106
- /* Zoom In Right */
107
- [data-animation='zoom-in-right'] {
108
- transform: scale(0.5) translateX(300px);
109
- }
110
-
111
- [data-animation='zoom-in-right'].is-visible {
112
- transform: scale(1) translateX(0);
113
- }
29
+ [data-animation='zoom-in'] { --scale: 0.3; }
30
+ [data-animation='zoom-out'] { --scale: 2; }
31
+ [data-animation='zoom-in-up'] { --scale: 0.5; --ty: var(--translate-distance, 300px); }
32
+ [data-animation='zoom-in-left'] { --scale: 0.5; --tx: calc(-1 * var(--translate-distance, 300px)); }
33
+ [data-animation='zoom-in-right'] { --scale: 0.5; --tx: var(--translate-distance, 300px); }
114
34
 
115
- /* Flip */
116
- [data-animation='flip'] {
117
- transform: perspective(1000px) rotateY(90deg);
118
- }
35
+ [data-animation='flip'] { --ry: 90deg; }
36
+ [data-animation='flip-x'] { --rx: 90deg; }
119
37
 
120
- [data-animation='flip'].is-visible {
121
- transform: perspective(1000px) rotateY(0deg);
122
- }
38
+ [data-animation='slide-rotate'] { --tx: calc(-1 * var(--translate-distance, 300px)); --rotate: -45deg; }
39
+ [data-animation='bounce-in'] { --scale: 0; }
123
40
 
124
- /* Flip X */
125
- [data-animation='flip-x'] {
126
- transform: perspective(1000px) rotateX(90deg);
127
- }
41
+ /* Visible state - reset transform values to final position */
42
+ [data-animation='fade-in-up'].is-visible { --ty: 0; }
43
+ [data-animation='fade-in-down'].is-visible { --ty: 0; }
44
+ [data-animation='fade-in-left'].is-visible { --tx: 0; }
45
+ [data-animation='fade-in-right'].is-visible { --tx: 0; }
128
46
 
129
- [data-animation='flip-x'].is-visible {
130
- transform: perspective(1000px) rotateX(0deg);
131
- }
47
+ [data-animation='zoom-in'].is-visible { --scale: 1; }
48
+ [data-animation='zoom-out'].is-visible { --scale: 1; }
49
+ [data-animation='zoom-in-up'].is-visible { --scale: 1; --ty: 0; }
50
+ [data-animation='zoom-in-left'].is-visible { --scale: 1; --tx: 0; }
51
+ [data-animation='zoom-in-right'].is-visible { --scale: 1; --tx: 0; }
132
52
 
133
- /* Slide Rotate */
134
- [data-animation='slide-rotate'] {
135
- transform: translateX(-300px) rotate(-45deg);
136
- }
53
+ [data-animation='flip'].is-visible { --ry: 0deg; }
54
+ [data-animation='flip-x'].is-visible { --rx: 0deg; }
137
55
 
138
- [data-animation='slide-rotate'].is-visible {
139
- transform: translateX(0) rotate(0deg);
140
- }
56
+ [data-animation='slide-rotate'].is-visible { --tx: 0; --rotate: 0deg; }
141
57
 
142
- /* Bounce In */
143
- [data-animation='bounce-in'] {
144
- transform: scale(0);
58
+ @keyframes bounce {
59
+ 0% { transform: scale(0); }
60
+ 50% { transform: scale(1.1); }
61
+ 100% { transform: scale(1); }
145
62
  }
146
63
 
64
+ /* Bounce animation - special case with keyframes */
147
65
  [data-animation='bounce-in'].is-visible {
148
- transform: scale(1);
149
- animation: bounce var(--duration, 800ms) cubic-bezier(0.68, -0.55, 0.265, 1.55);
66
+ animation: bounce var(--duration, 1500ms) cubic-bezier(0.68, -0.55, 0.265, 1.55);
150
67
  animation-delay: var(--delay, 0ms);
151
68
  }
152
69
 
153
- @keyframes bounce {
154
- 0% {
155
- transform: scale(0);
156
- }
157
- 50% {
158
- transform: scale(1.1);
159
- }
160
- 100% {
161
- transform: scale(1);
162
- }
163
- }
164
-
165
70
  /* Accessibility: Respect user's motion preferences */
166
71
  @media (prefers-reduced-motion: reduce) {
167
72
  .scroll-animate,
168
73
  .animated-element {
169
- /* Disable animations for users who prefer reduced motion */
170
74
  transition: none;
171
75
  animation: none !important;
172
76
  }
173
77
 
174
- /* Still show final state without animation */
175
78
  .scroll-animate.is-visible,
176
79
  .animated-element.is-visible {
177
80
  opacity: 1;
@@ -1,15 +1,18 @@
1
1
  /**
2
- * Animation type definitions and utilities
2
+ * Calculate rootMargin for IntersectionObserver from offset or custom rootMargin
3
+ *
4
+ * @param {number} [offset] - Viewport offset (0-100). 0 = bottom of viewport touches top of element, 100 = top of viewport touches top of element
5
+ * @param {string} [rootMargin] - Custom rootMargin string (takes precedence over offset)
6
+ * @returns {string} rootMargin string for IntersectionObserver
3
7
  */
8
+ export function calculateRootMargin(offset?: number, rootMargin?: string): string;
4
9
  /**
5
- * Type-safe animation names
6
- * Maps to CSS classes in animations.css
10
+ * Animation utilities
11
+ * Type definitions have been moved to types.js for single source of truth
7
12
  */
8
- export type AnimationType = 'fade-in' | 'fade-in-up' | 'fade-in-down' | 'fade-in-left' | 'fade-in-right' | 'zoom-in' | 'zoom-out' | 'zoom-in-up' | 'zoom-in-left' | 'zoom-in-right' | 'flip' | 'flip-x' | 'slide-rotate' | 'bounce-in';
9
13
  /**
10
- * Calculate rootMargin for IntersectionObserver from offset or custom rootMargin
11
- * @param offset - Viewport offset (0-100). 0 = bottom of viewport touches top of element, 100 = top of viewport touches top of element
12
- * @param rootMargin - Custom rootMargin string (takes precedence over offset)
13
- * @returns rootMargin string for IntersectionObserver
14
+ * All available animation types in the library
15
+ * Useful for programmatic access and validation
16
+ * @type {readonly string[]}
14
17
  */
15
- export declare function calculateRootMargin(offset?: number, rootMargin?: string): string;
18
+ export const ANIMATION_TYPES: readonly string[];
@@ -1,13 +1,38 @@
1
1
  /**
2
- * Animation type definitions and utilities
2
+ * Animation utilities
3
+ * Type definitions have been moved to types.js for single source of truth
3
4
  */
5
+
6
+ /**
7
+ * All available animation types in the library
8
+ * Useful for programmatic access and validation
9
+ * @type {readonly string[]}
10
+ */
11
+ export const ANIMATION_TYPES = [
12
+ 'fade-in',
13
+ 'fade-in-up',
14
+ 'fade-in-down',
15
+ 'fade-in-left',
16
+ 'fade-in-right',
17
+ 'zoom-in',
18
+ 'zoom-out',
19
+ 'zoom-in-up',
20
+ 'zoom-in-left',
21
+ 'zoom-in-right',
22
+ 'flip',
23
+ 'flip-x',
24
+ 'slide-rotate',
25
+ 'bounce-in'
26
+ ];
27
+
4
28
  /**
5
29
  * Calculate rootMargin for IntersectionObserver from offset or custom rootMargin
6
- * @param offset - Viewport offset (0-100). 0 = bottom of viewport touches top of element, 100 = top of viewport touches top of element
7
- * @param rootMargin - Custom rootMargin string (takes precedence over offset)
8
- * @returns rootMargin string for IntersectionObserver
30
+ *
31
+ * @param {number} [offset] - Viewport offset (0-100). 0 = bottom of viewport touches top of element, 100 = top of viewport touches top of element
32
+ * @param {string} [rootMargin] - Custom rootMargin string (takes precedence over offset)
33
+ * @returns {string} rootMargin string for IntersectionObserver
9
34
  */
10
35
  export function calculateRootMargin(offset, rootMargin) {
11
- return rootMargin ??
12
- (offset !== undefined ? `-${100 - offset}% 0px -${offset}% 0px` : '-10% 0px -10% 0px');
36
+ return rootMargin ??
37
+ (offset !== undefined ? `-${100 - offset}% 0px -${offset}% 0px` : '-10% 0px -10% 0px');
13
38
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @param {HTMLElement} element
3
+ * @param {number} [duration]
4
+ * @param {number} [delay=0]
5
+ */
6
+ export function setCSSVariables(element: HTMLElement, duration?: number, delay?: number): void;
7
+ /**
8
+ * @param {HTMLElement} element
9
+ * @param {import('./types.js').AnimationType} animation
10
+ */
11
+ export function setupAnimationElement(element: HTMLElement, animation: import("./types.js").AnimationType): void;
12
+ /**
13
+ * @param {HTMLElement} element
14
+ * @param {boolean} [debug=false]
15
+ * @param {number} [offset=0]
16
+ * @param {string} [sentinelColor='#00e0ff']
17
+ * @param {string} [debugLabel]
18
+ * @param {string} [sentinelId]
19
+ * @returns {{ element: HTMLElement, id: string }}
20
+ */
21
+ export function createSentinel(element: HTMLElement, debug?: boolean, offset?: number, sentinelColor?: string, debugLabel?: string, sentinelId?: string): {
22
+ element: HTMLElement;
23
+ id: string;
24
+ };
25
+ /**
26
+ * Check if CSS animations are loaded and warn if not (dev only)
27
+ * @returns {boolean} True if CSS appears to be loaded
28
+ */
29
+ export function checkAndWarnIfCSSNotLoaded(): boolean;