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.
@@ -1,75 +1,90 @@
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
- * Factory function to create intersection observer composables
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 (default: false)
7
+ * @param {import('./types.js').IntersectionOptions} [options={}]
8
+ * @param {((entry: IntersectionObserverEntry, isVisible: boolean) => void) | undefined} onIntersect
9
+ * @param {boolean} [once=false]
10
+ * @returns {import('./types.js').UseIntersectionReturn}
12
11
  */
13
- function createIntersectionObserver(options = {}, onIntersect, once = false) {
14
- const { threshold = 0.5, rootMargin = '-10% 0px -10% 0px', root = null } = options;
15
- let element = $state(null);
16
- let isVisible = $state(false);
17
- let hasTriggeredOnce = false;
18
- let observer = null;
19
- onMount(() => {
20
- if (!element)
21
- return;
22
- observer = new IntersectionObserver((entries) => {
23
- entries.forEach((entry) => {
24
- // For once-only behavior, check if already triggered
25
- if (once && hasTriggeredOnce)
26
- return;
27
- isVisible = entry.isIntersecting;
28
- onIntersect(entry, entry.isIntersecting);
29
- // Unobserve after first trigger if once=true
30
- if (once && entry.isIntersecting) {
31
- hasTriggeredOnce = true;
32
- observer?.unobserve(entry.target);
33
- }
34
- });
35
- }, {
36
- threshold,
37
- rootMargin,
38
- root
39
- });
40
- observer.observe(element);
41
- return () => {
42
- observer?.disconnect();
43
- };
44
- });
45
- return {
46
- get element() {
47
- return element;
48
- },
49
- set element(value) {
50
- element = value;
51
- },
52
- get isVisible() {
53
- return isVisible;
54
- }
55
- };
12
+ function createIntersectionObserver(options = {}, onIntersect = undefined, once = false) {
13
+ const { threshold = 0.5, rootMargin = '-10% 0px -10% 0px', root = null } = options;
14
+
15
+ let element = $state(null);
16
+ let isVisible = $state(false);
17
+ let hasTriggeredOnce = false;
18
+ /** @type {IntersectionObserver | null} */
19
+ let observer = null;
20
+
21
+ $effect(() => {
22
+ if (!element) return;
23
+
24
+ observer = new IntersectionObserver(
25
+ (entries) => {
26
+ entries.forEach((entry) => {
27
+ // For once-only behavior, check if already triggered
28
+ if (once && hasTriggeredOnce) return;
29
+
30
+ isVisible = entry.isIntersecting;
31
+ if (onIntersect) {
32
+ onIntersect(entry, entry.isIntersecting);
33
+ }
34
+
35
+ // Unobserve after first trigger if once=true
36
+ if (once && entry.isIntersecting) {
37
+ hasTriggeredOnce = true;
38
+ observer?.unobserve(entry.target);
39
+ }
40
+ });
41
+ },
42
+ {
43
+ threshold,
44
+ rootMargin,
45
+ root
46
+ }
47
+ );
48
+
49
+ observer.observe(element);
50
+
51
+ return () => {
52
+ observer?.disconnect();
53
+ };
54
+ });
55
+
56
+ return {
57
+ get element() {
58
+ return element;
59
+ },
60
+ set element(value) {
61
+ element = value;
62
+ },
63
+ get isVisible() {
64
+ return isVisible;
65
+ }
66
+ };
56
67
  }
68
+
57
69
  /**
58
- * Track element visibility with IntersectionObserver
59
- * Updates isVisible whenever visibility changes
60
- * @param options - IntersectionObserver configuration
61
- * @param onVisible - Optional callback when visibility changes
70
+ * @param {import('./types.js').IntersectionOptions} [options={}]
71
+ * @param {(isVisible: boolean) => void} [onVisible]
72
+ * @returns {import('./types.js').UseIntersectionReturn}
62
73
  */
63
74
  export function useIntersection(options = {}, onVisible) {
64
- return createIntersectionObserver(options, (_entry, isVisible) => {
65
- onVisible?.(isVisible);
66
- }, false);
75
+ return createIntersectionObserver(
76
+ options,
77
+ (_entry, isVisible) => {
78
+ onVisible?.(isVisible);
79
+ },
80
+ false
81
+ );
67
82
  }
83
+
68
84
  /**
69
- * Track element visibility once (until first trigger)
70
- * Unobserves after first visibility
71
- * @param options - IntersectionObserver configuration
85
+ * @param {import('./types.js').IntersectionOptions} [options={}]
86
+ * @returns {import('./types.js').UseIntersectionReturn}
72
87
  */
73
88
  export function useIntersectionOnce(options = {}) {
74
- return createIntersectionObserver(options, () => { }, true);
89
+ return createIntersectionObserver(options, () => {}, true);
75
90
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rune-scroller",
3
- "version": "1.0.0",
4
- "description": "Lightweight, high-performance scroll animations for Svelte 5. ~2KB bundle, zero dependencies.",
3
+ "version": "2.1.0",
4
+ "description": "Lightweight, high-performance scroll animations for Svelte 5. 12.7KB 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",
@@ -32,7 +32,9 @@
32
32
  "./animations.css": "./dist/animations.css"
33
33
  },
34
34
  "files": [
35
- "dist"
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
36
38
  ],
37
39
  "main": "./dist/index.js",
38
40
  "svelte": "./dist/index.js",
@@ -42,17 +44,17 @@
42
44
  },
43
45
  "scripts": {
44
46
  "dev": "vite dev",
45
- "build": "svelte-package && node scripts/fix-dist.js",
47
+ "build": "bunx svelte-package",
46
48
  "preview": "vite preview",
47
49
  "prepare": "svelte-kit sync || echo ''",
48
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
49
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
50
+ "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
51
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
50
52
  "format": "prettier --write .",
51
53
  "lint": "prettier --check . && eslint .",
52
- "prepublishonly": "pnpm run check && pnpm run build",
53
- "test:unit": "vitest",
54
- "test": "npm run test:unit -- --run"
54
+ "prepublishonly": "bun run check && bun run build && bun test",
55
+ "test": "bun test"
55
56
  },
57
+ "packageManager": "bun@1.3.4",
56
58
  "devDependencies": {
57
59
  "@eslint/compat": "^1.4.0",
58
60
  "@eslint/js": "^9.36.0",
@@ -64,13 +66,13 @@
64
66
  "eslint-config-prettier": "^10.1.8",
65
67
  "eslint-plugin-svelte": "^3.12.4",
66
68
  "globals": "^16.4.0",
69
+ "happy-dom": "^20.0.11",
67
70
  "prettier": "^3.6.2",
68
71
  "prettier-plugin-svelte": "^3.4.0",
69
72
  "svelte": "^5.39.5",
70
73
  "svelte-check": "^4.3.2",
71
74
  "typescript": "^5.9.2",
72
75
  "typescript-eslint": "^8.44.1",
73
- "vite": "^7.1.7",
74
- "vitest": "^3.2.4"
76
+ "vite": "^7.1.7"
75
77
  }
76
78
  }
@@ -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;
@@ -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;
@@ -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>;
@@ -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 +0,0 @@
1
- export {};
@@ -1,43 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { calculateRootMargin } from './animations';
3
- describe('calculateRootMargin', () => {
4
- it('calculates rootMargin from offset (0-100%)', () => {
5
- expect(calculateRootMargin(0)).toBe('-100% 0px -0% 0px');
6
- expect(calculateRootMargin(50)).toBe('-50% 0px -50% 0px');
7
- expect(calculateRootMargin(100)).toBe('-0% 0px -100% 0px');
8
- });
9
- it('uses custom rootMargin when provided', () => {
10
- const custom = '-10% 0px -10% 0px';
11
- expect(calculateRootMargin(50, custom)).toBe(custom);
12
- });
13
- it('uses default rootMargin when nothing provided', () => {
14
- expect(calculateRootMargin()).toBe('-10% 0px -10% 0px');
15
- });
16
- it('prioritizes custom rootMargin over offset', () => {
17
- const custom = '-20% 0px';
18
- expect(calculateRootMargin(50, custom)).toBe(custom);
19
- expect(calculateRootMargin(0, custom)).toBe(custom);
20
- });
21
- });
22
- describe('AnimationType', () => {
23
- it('includes all expected animation types', () => {
24
- const validAnimations = [
25
- 'fade-in',
26
- 'fade-in-up',
27
- 'fade-in-down',
28
- 'fade-in-left',
29
- 'fade-in-right',
30
- 'zoom-in',
31
- 'zoom-out',
32
- 'zoom-in-up',
33
- 'zoom-in-left',
34
- 'zoom-in-right',
35
- 'flip',
36
- 'flip-x',
37
- 'slide-rotate',
38
- 'bounce-in'
39
- ];
40
- // If this compiles, all types are valid
41
- expect(validAnimations.length).toBe(14);
42
- });
43
- });
@@ -1,23 +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 relative to element (no layout impact)
18
- * @param element - Reference element (used to position sentinel)
19
- * @param debug - If true, shows the sentinel as a visible line for debugging
20
- * @param offset - Offset in pixels from element bottom (default: 0, negative = above element)
21
- * @returns The created sentinel element
22
- */
23
- export declare function createSentinel(element: HTMLElement, debug?: boolean, offset?: number): HTMLElement;
@@ -1,48 +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 relative to element (no layout impact)
25
- * @param element - Reference element (used to position sentinel)
26
- * @param debug - If true, shows the sentinel as a visible line for debugging
27
- * @param offset - Offset in pixels from element bottom (default: 0, negative = above element)
28
- * @returns The created sentinel element
29
- */
30
- export function createSentinel(element, debug = false, offset = 0) {
31
- const sentinel = document.createElement('div');
32
- // Get element dimensions to position sentinel at its bottom + offset
33
- const rect = element.getBoundingClientRect();
34
- const elementHeight = rect.height;
35
- const sentinelTop = elementHeight + offset;
36
- if (debug) {
37
- // Debug mode: visible primary color line (cyan #00e0ff)
38
- sentinel.style.cssText =
39
- `position:absolute;top:${sentinelTop}px;left:0;right:0;height:3px;background:#00e0ff;margin:0;padding:0;box-sizing:border-box;z-index:999;pointer-events:none`;
40
- sentinel.setAttribute('data-sentinel-debug', 'true');
41
- }
42
- else {
43
- // Production: invisible positioned absolutely (no layout impact)
44
- sentinel.style.cssText =
45
- `position:absolute;top:${sentinelTop}px;left:0;right:0;height:1px;visibility:hidden;margin:0;padding:0;box-sizing:border-box;pointer-events:none`;
46
- }
47
- return sentinel;
48
- }
@@ -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
- };
@@ -1,83 +0,0 @@
1
- import { setCSSVariables, setupAnimationElement, createSentinel } from './dom-utils.svelte';
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 function runeScroller(element, options) {
22
- // Setup animation classes et variables CSS
23
- if (options?.animation || options?.duration) {
24
- setupAnimationElement(element, options.animation);
25
- setCSSVariables(element, options.duration);
26
- }
27
- // Créer un wrapper div autour de l'élément pour le sentinel en position absolute
28
- // Ceci évite de casser le flex/grid flow du parent
29
- const wrapper = document.createElement('div');
30
- wrapper.style.cssText = 'position:relative;display:contents';
31
- // Insérer le wrapper avant l'élément
32
- element.insertAdjacentElement('beforebegin', wrapper);
33
- wrapper.appendChild(element);
34
- // Créer le sentinel invisible (ou visible si debug=true)
35
- // Sentinel positioned absolutely relative to wrapper
36
- const sentinel = createSentinel(element, options?.debug, options?.offset);
37
- wrapper.appendChild(sentinel);
38
- // Observer le sentinel avec cleanup tracking
39
- let observerConnected = true;
40
- const observer = new IntersectionObserver((entries) => {
41
- const isIntersecting = entries[0].isIntersecting;
42
- if (isIntersecting) {
43
- // Ajouter la classe is-visible à l'élément
44
- element.classList.add('is-visible');
45
- // Déconnecter si pas en mode repeat
46
- if (!options?.repeat) {
47
- observer.disconnect();
48
- observerConnected = false;
49
- }
50
- }
51
- else if (options?.repeat) {
52
- // En mode repeat, retirer la classe quand le sentinel sort
53
- element.classList.remove('is-visible');
54
- }
55
- }, { threshold: 0 });
56
- observer.observe(sentinel);
57
- return {
58
- update(newOptions) {
59
- if (newOptions?.animation) {
60
- element.setAttribute('data-animation', newOptions.animation);
61
- }
62
- if (newOptions?.duration) {
63
- setCSSVariables(element, newOptions.duration);
64
- }
65
- // Update repeat option
66
- if (newOptions?.repeat !== undefined && newOptions.repeat !== options?.repeat) {
67
- options = { ...options, repeat: newOptions.repeat };
68
- }
69
- },
70
- destroy() {
71
- if (observerConnected) {
72
- observer.disconnect();
73
- }
74
- sentinel.remove();
75
- // Unwrap element (move it out of wrapper)
76
- const parent = wrapper.parentElement;
77
- if (parent) {
78
- wrapper.insertAdjacentElement('beforebegin', element);
79
- }
80
- wrapper.remove();
81
- }
82
- };
83
- }
@@ -1 +0,0 @@
1
- export {};