rune-scroller 0.1.11 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -29
- package/dist/__mocks__/IntersectionObserver.d.ts +25 -0
- package/dist/__mocks__/IntersectionObserver.js +116 -0
- package/dist/__mocks__/svelte-runes.d.ts +25 -0
- package/dist/__mocks__/svelte-runes.js +117 -0
- package/dist/__test-helpers__/dom.d.ts +118 -0
- package/dist/__test-helpers__/dom.js +305 -0
- package/dist/animate.d.ts +4 -0
- package/dist/animate.js +152 -0
- package/dist/animate.test.js +370 -0
- package/dist/animations.comprehensive.test.d.ts +1 -0
- package/dist/animations.comprehensive.test.js +432 -0
- package/dist/animations.css +21 -12
- package/dist/animations.d.ts +12 -9
- package/dist/animations.js +31 -6
- package/dist/animations.test.js +23 -41
- package/dist/dom-utils.d.ts +40 -0
- package/dist/dom-utils.js +111 -0
- package/dist/dom-utils.test.d.ts +1 -0
- package/dist/dom-utils.test.js +220 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +17 -4
- package/dist/observer-utils.d.ts +40 -0
- package/dist/observer-utils.js +50 -0
- package/dist/robustness.test.d.ts +1 -0
- package/dist/robustness.test.js +317 -0
- package/dist/runeScroller.d.ts +25 -0
- package/dist/runeScroller.integration.test.d.ts +1 -0
- package/dist/runeScroller.integration.test.js +419 -0
- package/dist/runeScroller.js +183 -0
- package/dist/runeScroller.test.d.ts +1 -0
- package/dist/runeScroller.test.js +375 -0
- package/dist/types.d.ts +104 -24
- package/dist/types.js +58 -0
- package/dist/useIntersection.svelte.d.ts +7 -12
- package/dist/useIntersection.svelte.js +75 -54
- package/dist/useIntersection.test.d.ts +1 -0
- package/dist/useIntersection.test.js +98 -0
- package/package.json +19 -18
- package/dist/BaseAnimated.svelte +0 -48
- package/dist/BaseAnimated.svelte.d.ts +0 -16
- package/dist/RuneScroller.svelte +0 -37
- package/dist/RuneScroller.svelte.d.ts +0 -16
- package/dist/animate.svelte.d.ts +0 -14
- package/dist/animate.svelte.js +0 -79
- package/dist/dom-utils.svelte.d.ts +0 -22
- package/dist/dom-utils.svelte.js +0 -46
- package/dist/runeScroller.svelte.d.ts +0 -24
- package/dist/runeScroller.svelte.js +0 -79
- package/dist/scroll-animate.test.js +0 -57
- /package/dist/{scroll-animate.test.d.ts → animate.test.d.ts} +0 -0
|
@@ -1,75 +1,96 @@
|
|
|
1
|
-
import { onMount } from 'svelte';
|
|
2
1
|
/**
|
|
3
2
|
* Composable for handling IntersectionObserver logic
|
|
4
3
|
* Reduces duplication between animation components
|
|
5
4
|
*/
|
|
5
|
+
|
|
6
6
|
/**
|
|
7
7
|
* Factory function to create intersection observer composables
|
|
8
8
|
* Eliminates duplication between useIntersection and useIntersectionOnce
|
|
9
|
-
* @param options - IntersectionObserver configuration
|
|
10
|
-
* @param onIntersect - Callback handler for intersection changes
|
|
11
|
-
* @param once - Whether to trigger only once
|
|
9
|
+
* @param {import('./types.js').IntersectionOptions} [options={}] - IntersectionObserver configuration
|
|
10
|
+
* @param {((entry: IntersectionObserverEntry, isVisible: boolean) => void) | undefined} onIntersect - Callback handler for intersection changes
|
|
11
|
+
* @param {boolean} [once=false] - Whether to trigger only once
|
|
12
|
+
* @returns {import('./types.js').UseIntersectionReturn}
|
|
12
13
|
*/
|
|
13
|
-
function createIntersectionObserver(options = {}, onIntersect, once = false) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
14
|
+
function createIntersectionObserver(options = {}, onIntersect = undefined, once = false) {
|
|
15
|
+
const { threshold = 0.5, rootMargin = '-10% 0px -10% 0px', root = null } = options;
|
|
16
|
+
|
|
17
|
+
let element = $state(null);
|
|
18
|
+
let isVisible = $state(false);
|
|
19
|
+
let hasTriggeredOnce = false;
|
|
20
|
+
/** @type {IntersectionObserver | null} */
|
|
21
|
+
let observer = null;
|
|
22
|
+
|
|
23
|
+
$effect(() => {
|
|
24
|
+
if (!element) return;
|
|
25
|
+
|
|
26
|
+
observer = new IntersectionObserver(
|
|
27
|
+
(entries) => {
|
|
28
|
+
entries.forEach((entry) => {
|
|
29
|
+
// For once-only behavior, check if already triggered
|
|
30
|
+
if (once && hasTriggeredOnce) return;
|
|
31
|
+
|
|
32
|
+
isVisible = entry.isIntersecting;
|
|
33
|
+
if (onIntersect) {
|
|
34
|
+
onIntersect(entry, entry.isIntersecting);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Unobserve after first trigger if once=true
|
|
38
|
+
if (once && entry.isIntersecting) {
|
|
39
|
+
hasTriggeredOnce = true;
|
|
40
|
+
observer?.unobserve(entry.target);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
threshold,
|
|
46
|
+
rootMargin,
|
|
47
|
+
root
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
observer.observe(element);
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
observer?.disconnect();
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
get element() {
|
|
60
|
+
return element;
|
|
61
|
+
},
|
|
62
|
+
set element(value) {
|
|
63
|
+
element = value;
|
|
64
|
+
},
|
|
65
|
+
get isVisible() {
|
|
66
|
+
return isVisible;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
56
69
|
}
|
|
70
|
+
|
|
57
71
|
/**
|
|
58
72
|
* Track element visibility with IntersectionObserver
|
|
59
73
|
* Updates isVisible whenever visibility changes
|
|
60
|
-
* @param options - IntersectionObserver configuration
|
|
61
|
-
* @param onVisible - Optional callback when visibility changes
|
|
74
|
+
* @param {import('./types.js').IntersectionOptions} [options={}] - IntersectionObserver configuration
|
|
75
|
+
* @param {(isVisible: boolean) => void} [onVisible] - Optional callback when visibility changes
|
|
76
|
+
* @returns {import('./types.js').UseIntersectionReturn}
|
|
62
77
|
*/
|
|
63
78
|
export function useIntersection(options = {}, onVisible) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
return createIntersectionObserver(
|
|
80
|
+
options,
|
|
81
|
+
(_entry, isVisible) => {
|
|
82
|
+
onVisible?.(isVisible);
|
|
83
|
+
},
|
|
84
|
+
false
|
|
85
|
+
);
|
|
67
86
|
}
|
|
87
|
+
|
|
68
88
|
/**
|
|
69
89
|
* Track element visibility once (until first trigger)
|
|
70
90
|
* Unobserves after first visibility
|
|
71
|
-
* @param options - IntersectionObserver configuration
|
|
91
|
+
* @param {import('./types.js').IntersectionOptions} [options={}] - IntersectionObserver configuration
|
|
92
|
+
* @returns {import('./types.js').UseIntersectionReturn}
|
|
72
93
|
*/
|
|
73
94
|
export function useIntersectionOnce(options = {}) {
|
|
74
|
-
|
|
95
|
+
return createIntersectionObserver(options, () => {}, true);
|
|
75
96
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Structural tests for useIntersection and useIntersectionOnce composables
|
|
5
|
+
*
|
|
6
|
+
* Note: Full functional testing requires Svelte 5 runtime environment.
|
|
7
|
+
* These composables use Svelte runes ($state) and onMount hook which cannot
|
|
8
|
+
* be tested outside of a Svelte component context.
|
|
9
|
+
*
|
|
10
|
+
* These tests verify:
|
|
11
|
+
* - Module exports exist
|
|
12
|
+
* - File structure is valid
|
|
13
|
+
* - No syntax errors when imported
|
|
14
|
+
*/
|
|
15
|
+
describe('useIntersection Composable', () => {
|
|
16
|
+
let useIntersection;
|
|
17
|
+
let useIntersectionOnce;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Import composables
|
|
21
|
+
const module = require('./useIntersection.svelte.js');
|
|
22
|
+
useIntersection = module.useIntersection;
|
|
23
|
+
useIntersectionOnce = module.useIntersectionOnce;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('Module Exports', () => {
|
|
27
|
+
it('exports useIntersection function', () => {
|
|
28
|
+
expect(useIntersection).toBeDefined();
|
|
29
|
+
expect(typeof useIntersection).toBe('function');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('exports useIntersectionOnce function', () => {
|
|
33
|
+
expect(useIntersectionOnce).toBeDefined();
|
|
34
|
+
expect(typeof useIntersectionOnce).toBe('function');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('both exports are functions', () => {
|
|
38
|
+
expect(typeof useIntersection).toBe('function');
|
|
39
|
+
expect(typeof useIntersectionOnce).toBe('function');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('File Structure and Syntax', () => {
|
|
44
|
+
it('module is importable without errors', () => {
|
|
45
|
+
expect(() => {
|
|
46
|
+
require('./useIntersection.svelte.js');
|
|
47
|
+
}).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('has proper JSDoc comments', () => {
|
|
51
|
+
// This verifies the file has documentation
|
|
52
|
+
const fs = require('fs');
|
|
53
|
+
const { fileURLToPath } = require('url');
|
|
54
|
+
const { dirname } = require('path');
|
|
55
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
56
|
+
const __dirname = dirname(__filename);
|
|
57
|
+
const filePath = `${__dirname}/useIntersection.svelte.js`;
|
|
58
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
59
|
+
expect(content).toContain('@param');
|
|
60
|
+
expect(content).toContain('Composable');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('uses Svelte 5 runes correctly', () => {
|
|
64
|
+
const fs = require('fs');
|
|
65
|
+
const { fileURLToPath } = require('url');
|
|
66
|
+
const { dirname } = require('path');
|
|
67
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
68
|
+
const __dirname = dirname(__filename);
|
|
69
|
+
const filePath = `${__dirname}/useIntersection.svelte.js`;
|
|
70
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
71
|
+
expect(content).toContain('$state');
|
|
72
|
+
expect(content).toContain('$effect');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('Integration Notes', () => {
|
|
77
|
+
it('documentation indicates Svelte environment requirement', () => {
|
|
78
|
+
const fs = require('fs');
|
|
79
|
+
const { fileURLToPath } = require('url');
|
|
80
|
+
const { dirname } = require('path');
|
|
81
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
82
|
+
const __dirname = dirname(__filename);
|
|
83
|
+
const filePath = `${__dirname}/useIntersection.svelte.js`;
|
|
84
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
85
|
+
// Should mention IntersectionObserver usage
|
|
86
|
+
expect(content).toContain('IntersectionObserver');
|
|
87
|
+
// Should mention $effect lifecycle (Svelte 5 best practice)
|
|
88
|
+
expect(content).toContain('$effect');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('exports are accessible for import', () => {
|
|
92
|
+
// Verify we can destructure the exports
|
|
93
|
+
const { useIntersection, useIntersectionOnce } = require('./useIntersection.svelte.js');
|
|
94
|
+
expect(useIntersection).toBeDefined();
|
|
95
|
+
expect(useIntersectionOnce).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rune-scroller",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Lightweight, high-performance scroll animations for Svelte 5. ~
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Lightweight, high-performance scroll animations for Svelte 5. ~3.4KB gzipped, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"license": "MIT",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/lelabdev/rune-scroller"
|
|
14
|
+
"url": "git+https://github.com/lelabdev/rune-scroller.git"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"svelte",
|
|
@@ -40,6 +40,19 @@
|
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"svelte": "^5.0.0"
|
|
42
42
|
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"dev": "vite dev",
|
|
45
|
+
"build": "bunx svelte-package",
|
|
46
|
+
"preview": "vite preview",
|
|
47
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
48
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
|
49
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
|
50
|
+
"format": "prettier --write .",
|
|
51
|
+
"lint": "prettier --check . && eslint .",
|
|
52
|
+
"prepublishonly": "bun run check && bun run build && bun test",
|
|
53
|
+
"test": "bun test"
|
|
54
|
+
},
|
|
55
|
+
"packageManager": "bun@1.3.4",
|
|
43
56
|
"devDependencies": {
|
|
44
57
|
"@eslint/compat": "^1.4.0",
|
|
45
58
|
"@eslint/js": "^9.36.0",
|
|
@@ -51,25 +64,13 @@
|
|
|
51
64
|
"eslint-config-prettier": "^10.1.8",
|
|
52
65
|
"eslint-plugin-svelte": "^3.12.4",
|
|
53
66
|
"globals": "^16.4.0",
|
|
67
|
+
"happy-dom": "^20.0.11",
|
|
54
68
|
"prettier": "^3.6.2",
|
|
55
69
|
"prettier-plugin-svelte": "^3.4.0",
|
|
56
70
|
"svelte": "^5.39.5",
|
|
57
71
|
"svelte-check": "^4.3.2",
|
|
58
72
|
"typescript": "^5.9.2",
|
|
59
73
|
"typescript-eslint": "^8.44.1",
|
|
60
|
-
"vite": "^7.1.7"
|
|
61
|
-
"vitest": "^3.2.4"
|
|
62
|
-
},
|
|
63
|
-
"scripts": {
|
|
64
|
-
"dev": "vite dev",
|
|
65
|
-
"build": "svelte-package && node scripts/fix-dist.js",
|
|
66
|
-
"preview": "vite preview",
|
|
67
|
-
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
68
|
-
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
69
|
-
"format": "prettier --write .",
|
|
70
|
-
"lint": "prettier --check . && eslint .",
|
|
71
|
-
"prepublishonly": "pnpm run check && pnpm run build",
|
|
72
|
-
"test:unit": "vitest",
|
|
73
|
-
"test": "npm run test:unit -- --run"
|
|
74
|
+
"vite": "^7.1.7"
|
|
74
75
|
}
|
|
75
|
-
}
|
|
76
|
+
}
|
package/dist/BaseAnimated.svelte
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import { useIntersection, useIntersectionOnce } from './useIntersection.svelte';
|
|
4
|
-
import { calculateRootMargin, type AnimationType } from './animations';
|
|
5
|
-
import './animations.css';
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
animation?: AnimationType;
|
|
9
|
-
threshold?: number;
|
|
10
|
-
rootMargin?: string;
|
|
11
|
-
offset?: number;
|
|
12
|
-
duration?: number;
|
|
13
|
-
delay?: number;
|
|
14
|
-
once?: boolean;
|
|
15
|
-
children: Snippet;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const {
|
|
19
|
-
animation = 'fade-in',
|
|
20
|
-
threshold = 0.5,
|
|
21
|
-
rootMargin,
|
|
22
|
-
offset,
|
|
23
|
-
duration = 800,
|
|
24
|
-
delay = 0,
|
|
25
|
-
once = false,
|
|
26
|
-
children
|
|
27
|
-
}: Props = $props();
|
|
28
|
-
|
|
29
|
-
// Calculate rootMargin from offset if provided
|
|
30
|
-
const finalRootMargin = calculateRootMargin(offset, rootMargin);
|
|
31
|
-
|
|
32
|
-
// Use appropriate composable based on once prop
|
|
33
|
-
const intersection = once
|
|
34
|
-
? useIntersectionOnce({ threshold, rootMargin: finalRootMargin })
|
|
35
|
-
: useIntersection({ threshold, rootMargin: finalRootMargin });
|
|
36
|
-
</script>
|
|
37
|
-
|
|
38
|
-
<div
|
|
39
|
-
bind:this={intersection.element}
|
|
40
|
-
class="scroll-animate"
|
|
41
|
-
class:is-visible={intersection.isVisible}
|
|
42
|
-
data-animation={animation}
|
|
43
|
-
style="--duration: {duration}ms; --delay: {delay}ms;"
|
|
44
|
-
>
|
|
45
|
-
{@render children()}
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<!-- Styles are imported from animations.css -->
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
2
|
-
import { type AnimationType } from './animations';
|
|
3
|
-
import './animations.css';
|
|
4
|
-
interface Props {
|
|
5
|
-
animation?: AnimationType;
|
|
6
|
-
threshold?: number;
|
|
7
|
-
rootMargin?: string;
|
|
8
|
-
offset?: number;
|
|
9
|
-
duration?: number;
|
|
10
|
-
delay?: number;
|
|
11
|
-
once?: boolean;
|
|
12
|
-
children: Snippet;
|
|
13
|
-
}
|
|
14
|
-
declare const BaseAnimated: import("svelte").Component<Props, {}, "">;
|
|
15
|
-
type BaseAnimated = ReturnType<typeof BaseAnimated>;
|
|
16
|
-
export default BaseAnimated;
|
package/dist/RuneScroller.svelte
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import type { AnimationType } from './animations';
|
|
4
|
-
import BaseAnimated from './BaseAnimated.svelte';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
animation?: AnimationType;
|
|
8
|
-
threshold?: number;
|
|
9
|
-
rootMargin?: string;
|
|
10
|
-
offset?: number;
|
|
11
|
-
duration?: number;
|
|
12
|
-
delay?: number;
|
|
13
|
-
repeat?: boolean;
|
|
14
|
-
children: Snippet;
|
|
15
|
-
[key: string]: any;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const {
|
|
19
|
-
animation = 'fade-in',
|
|
20
|
-
threshold = 0.5,
|
|
21
|
-
rootMargin,
|
|
22
|
-
offset,
|
|
23
|
-
duration = 800,
|
|
24
|
-
delay = 0,
|
|
25
|
-
repeat = false,
|
|
26
|
-
children,
|
|
27
|
-
...rest
|
|
28
|
-
}: Props = $props();
|
|
29
|
-
</script>
|
|
30
|
-
|
|
31
|
-
<!--
|
|
32
|
-
* Wrapper component for scroll animations
|
|
33
|
-
* Triggers animation based on repeat setting:
|
|
34
|
-
* - repeat={false} (default): triggers once when element enters viewport
|
|
35
|
-
* - repeat={true}: triggers each time element enters viewport
|
|
36
|
-
-->
|
|
37
|
-
<BaseAnimated {animation} {threshold} {rootMargin} {offset} {duration} {delay} once={!repeat} {children} {...rest} />
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { AnimationType } from './animations';
|
|
3
|
-
interface Props {
|
|
4
|
-
animation?: AnimationType;
|
|
5
|
-
threshold?: number;
|
|
6
|
-
rootMargin?: string;
|
|
7
|
-
offset?: number;
|
|
8
|
-
duration?: number;
|
|
9
|
-
delay?: number;
|
|
10
|
-
repeat?: boolean;
|
|
11
|
-
children: Snippet;
|
|
12
|
-
[key: string]: any;
|
|
13
|
-
}
|
|
14
|
-
declare const RuneScroller: import("svelte").Component<Props, {}, "">;
|
|
15
|
-
type RuneScroller = ReturnType<typeof RuneScroller>;
|
|
16
|
-
export default RuneScroller;
|
package/dist/animate.svelte.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Action } from 'svelte/action';
|
|
2
|
-
import type { AnimateOptions } from './types';
|
|
3
|
-
/**
|
|
4
|
-
* Svelte action for scroll animations
|
|
5
|
-
* Triggers animation once when element enters viewport
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```svelte
|
|
9
|
-
* <div use:animate={{ animation: 'fade-up', duration: 1000 }}>
|
|
10
|
-
* Content
|
|
11
|
-
* </div>
|
|
12
|
-
* ```
|
|
13
|
-
*/
|
|
14
|
-
export declare const animate: Action<HTMLElement, AnimateOptions>;
|
package/dist/animate.svelte.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { calculateRootMargin } from './animations';
|
|
2
|
-
import { setCSSVariables, setupAnimationElement } from './dom-utils.svelte';
|
|
3
|
-
/**
|
|
4
|
-
* Svelte action for scroll animations
|
|
5
|
-
* Triggers animation once when element enters viewport
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```svelte
|
|
9
|
-
* <div use:animate={{ animation: 'fade-up', duration: 1000 }}>
|
|
10
|
-
* Content
|
|
11
|
-
* </div>
|
|
12
|
-
* ```
|
|
13
|
-
*/
|
|
14
|
-
export const animate = (node, options = {}) => {
|
|
15
|
-
let { animation = 'fade-in', duration = 800, delay = 0, offset, threshold = 0, rootMargin } = options;
|
|
16
|
-
// Calculate rootMargin from offset (0-100%)
|
|
17
|
-
let finalRootMargin = calculateRootMargin(offset, rootMargin);
|
|
18
|
-
// Setup animation with utilities
|
|
19
|
-
setupAnimationElement(node, animation);
|
|
20
|
-
setCSSVariables(node, duration, delay);
|
|
21
|
-
// Track if animation has been triggered
|
|
22
|
-
let animated = false;
|
|
23
|
-
let observerConnected = true;
|
|
24
|
-
// Create IntersectionObserver for one-time animation
|
|
25
|
-
const observer = new IntersectionObserver((entries) => {
|
|
26
|
-
entries.forEach((entry) => {
|
|
27
|
-
// Trigger animation once when element enters viewport
|
|
28
|
-
if (entry.isIntersecting && !animated) {
|
|
29
|
-
node.classList.add('is-visible');
|
|
30
|
-
animated = true;
|
|
31
|
-
// Stop observing after animation triggers
|
|
32
|
-
observer.unobserve(node);
|
|
33
|
-
observerConnected = false;
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
}, {
|
|
37
|
-
threshold,
|
|
38
|
-
rootMargin: finalRootMargin
|
|
39
|
-
});
|
|
40
|
-
observer.observe(node);
|
|
41
|
-
return {
|
|
42
|
-
update(newOptions) {
|
|
43
|
-
const { duration: newDuration, delay: newDelay, animation: newAnimation, offset: newOffset, threshold: newThreshold, rootMargin: newRootMargin } = newOptions;
|
|
44
|
-
// Update CSS properties
|
|
45
|
-
if (newDuration !== undefined) {
|
|
46
|
-
duration = newDuration;
|
|
47
|
-
setCSSVariables(node, duration, newDelay ?? delay);
|
|
48
|
-
}
|
|
49
|
-
if (newDelay !== undefined && newDelay !== delay) {
|
|
50
|
-
delay = newDelay;
|
|
51
|
-
setCSSVariables(node, duration, delay);
|
|
52
|
-
}
|
|
53
|
-
if (newAnimation && newAnimation !== animation) {
|
|
54
|
-
animation = newAnimation;
|
|
55
|
-
node.setAttribute('data-animation', newAnimation);
|
|
56
|
-
}
|
|
57
|
-
// Recreate observer if threshold or rootMargin changed
|
|
58
|
-
if (newThreshold !== undefined || newOffset !== undefined || newRootMargin !== undefined) {
|
|
59
|
-
if (observerConnected) {
|
|
60
|
-
observer.disconnect();
|
|
61
|
-
observerConnected = false;
|
|
62
|
-
}
|
|
63
|
-
threshold = newThreshold ?? threshold;
|
|
64
|
-
offset = newOffset ?? offset;
|
|
65
|
-
rootMargin = newRootMargin ?? rootMargin;
|
|
66
|
-
finalRootMargin = calculateRootMargin(offset, rootMargin);
|
|
67
|
-
if (!animated) {
|
|
68
|
-
observer.observe(node);
|
|
69
|
-
observerConnected = true;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
destroy() {
|
|
74
|
-
if (observerConnected) {
|
|
75
|
-
observer.disconnect();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { AnimationType } from './animations';
|
|
2
|
-
/**
|
|
3
|
-
* Set CSS custom properties on an element
|
|
4
|
-
* @param element - Target DOM element
|
|
5
|
-
* @param duration - Animation duration in milliseconds
|
|
6
|
-
* @param delay - Animation delay in milliseconds
|
|
7
|
-
*/
|
|
8
|
-
export declare function setCSSVariables(element: HTMLElement, duration?: number, delay?: number): void;
|
|
9
|
-
/**
|
|
10
|
-
* Setup animation element with required classes and attributes
|
|
11
|
-
* @param element - Target DOM element
|
|
12
|
-
* @param animation - Animation type to apply
|
|
13
|
-
*/
|
|
14
|
-
export declare function setupAnimationElement(element: HTMLElement, animation: AnimationType): void;
|
|
15
|
-
/**
|
|
16
|
-
* Create sentinel element for observer-based triggering
|
|
17
|
-
* Positioned absolutely after element, stays fixed while element animates
|
|
18
|
-
* @param element - Reference element (used to position sentinel at its bottom)
|
|
19
|
-
* @param debug - If true, shows the sentinel as a visible line for debugging
|
|
20
|
-
* @returns The created sentinel element
|
|
21
|
-
*/
|
|
22
|
-
export declare function createSentinel(element: HTMLElement, debug?: boolean): HTMLElement;
|
package/dist/dom-utils.svelte.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Set CSS custom properties on an element
|
|
3
|
-
* @param element - Target DOM element
|
|
4
|
-
* @param duration - Animation duration in milliseconds
|
|
5
|
-
* @param delay - Animation delay in milliseconds
|
|
6
|
-
*/
|
|
7
|
-
export function setCSSVariables(element, duration, delay = 0) {
|
|
8
|
-
if (duration !== undefined) {
|
|
9
|
-
element.style.setProperty('--duration', `${duration}ms`);
|
|
10
|
-
}
|
|
11
|
-
element.style.setProperty('--delay', `${delay}ms`);
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Setup animation element with required classes and attributes
|
|
15
|
-
* @param element - Target DOM element
|
|
16
|
-
* @param animation - Animation type to apply
|
|
17
|
-
*/
|
|
18
|
-
export function setupAnimationElement(element, animation) {
|
|
19
|
-
element.classList.add('scroll-animate');
|
|
20
|
-
element.setAttribute('data-animation', animation);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Create sentinel element for observer-based triggering
|
|
24
|
-
* Positioned absolutely after element, stays fixed while element animates
|
|
25
|
-
* @param element - Reference element (used to position sentinel at its bottom)
|
|
26
|
-
* @param debug - If true, shows the sentinel as a visible line for debugging
|
|
27
|
-
* @returns The created sentinel element
|
|
28
|
-
*/
|
|
29
|
-
export function createSentinel(element, debug = false) {
|
|
30
|
-
const sentinel = document.createElement('div');
|
|
31
|
-
// Get element dimensions to position sentinel at its bottom
|
|
32
|
-
const rect = element.getBoundingClientRect();
|
|
33
|
-
const elementHeight = rect.height;
|
|
34
|
-
if (debug) {
|
|
35
|
-
// Debug mode: visible primary color line (cyan #00e0ff)
|
|
36
|
-
sentinel.style.cssText =
|
|
37
|
-
`position:absolute;top:${elementHeight}px;left:0;right:0;height:3px;background:#00e0ff;margin:0;padding:0;box-sizing:border-box;z-index:999;pointer-events:none`;
|
|
38
|
-
sentinel.setAttribute('data-sentinel-debug', 'true');
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
// Production: invisible positioned absolutely (no layout impact)
|
|
42
|
-
sentinel.style.cssText =
|
|
43
|
-
`position:absolute;top:${elementHeight}px;left:0;right:0;height:1px;visibility:hidden;margin:0;padding:0;box-sizing:border-box;pointer-events:none`;
|
|
44
|
-
}
|
|
45
|
-
return sentinel;
|
|
46
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { RuneScrollerOptions } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Action pour animer un élément au scroll avec un sentinel invisible juste en dessous
|
|
4
|
-
* @param element - L'élément à animer
|
|
5
|
-
* @param options - Options d'animation (animation type, duration, et repeat)
|
|
6
|
-
* @returns Objet action Svelte
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```svelte
|
|
10
|
-
* <!-- Animation une seule fois -->
|
|
11
|
-
* <div use:runeScroller={{ animation: 'fade-in-up', duration: 1000 }}>
|
|
12
|
-
* Content
|
|
13
|
-
* </div>
|
|
14
|
-
*
|
|
15
|
-
* <!-- Animation répétée à chaque scroll -->
|
|
16
|
-
* <div use:runeScroller={{ animation: 'fade-in-up', duration: 1000, repeat: true }}>
|
|
17
|
-
* Content
|
|
18
|
-
* </div>
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export declare function runeScroller(element: HTMLElement, options?: RuneScrollerOptions): {
|
|
22
|
-
update(newOptions?: RuneScrollerOptions): void;
|
|
23
|
-
destroy(): void;
|
|
24
|
-
};
|