@ariefsn/svelte-sentinel 1.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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arief Setiyo Nugroho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,285 @@
1
+ # Svelte Sentinel
2
+
3
+ ![svelte-sentinel feature banner](./static/feature.svg)
4
+
5
+ [![npm](https://img.shields.io/npm/v/@ariefsn/svelte-sentinel?color=ff3e00&logo=npm)](https://www.npmjs.com/package/@ariefsn/svelte-sentinel)
6
+ [![GitHub](https://img.shields.io/badge/GitHub-ariefsn%2Fsvelte--sentinel-181717?logo=github)](https://github.com/ariefsn/svelte-sentinel)
7
+ [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE.md)
8
+
9
+ A lightweight **Svelte 5** component that wraps the browser's [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) API, letting you declaratively respond to an element entering or leaving the viewport.
10
+
11
+ - Zero external dependencies
12
+ - Svelte 5 runes (`$props`, `$state`, `$bindable`)
13
+ - Full TypeScript support
14
+ - Callbacks, bindable state, and conditional snippet slots
15
+
16
+ ---
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ # npm
22
+ npm install @ariefsn/svelte-sentinel
23
+
24
+ # pnpm
25
+ pnpm add @ariefsn/svelte-sentinel
26
+
27
+ # yarn
28
+ yarn add @ariefsn/svelte-sentinel
29
+
30
+ # bun
31
+ bun add @ariefsn/svelte-sentinel
32
+ ```
33
+
34
+ **Peer dependency:** Svelte 5 (`^5.0.0`)
35
+
36
+ ---
37
+
38
+ ## Quick Start
39
+
40
+ ```svelte
41
+ <script>
42
+ import { Sentinel } from '@ariefsn/svelte-sentinel';
43
+ </script>
44
+
45
+ <Sentinel onEnter={() => console.log('in view!')} onExit={() => console.log('out of view!')}>
46
+ <p>Watch me scroll!</p>
47
+ </Sentinel>
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Usage
53
+
54
+ ### Basic visibility callbacks
55
+
56
+ Use `onEnter`, `onExit`, or `onViewChange` to react to visibility changes.
57
+ Each callback receives the raw [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) for full control.
58
+
59
+ ```svelte
60
+ <Sentinel
61
+ onEnter={(entry) => console.log('entered', entry.intersectionRatio)}
62
+ onExit={() => console.log('exited')}
63
+ onViewChange={(isVisible) => console.log('visible:', isVisible)}
64
+ >
65
+ <div>Observed element</div>
66
+ </Sentinel>
67
+ ```
68
+
69
+ ---
70
+
71
+ ### Reactive state with `bind:isVisible`
72
+
73
+ Bind the `isVisible` prop to read the element's visibility reactively anywhere in the parent component — no callback needed.
74
+
75
+ ```svelte
76
+ <script>
77
+ import { Sentinel } from '@ariefsn/svelte-sentinel';
78
+
79
+ let visible = $state(false);
80
+ </script>
81
+
82
+ <header class:scrolled={!visible}>Sticky header</header>
83
+
84
+ <Sentinel bind:isVisible={visible}>
85
+ <h1>Page hero</h1>
86
+ </Sentinel>
87
+ ```
88
+
89
+ ---
90
+
91
+ ### Animate on scroll (one-shot with `once`)
92
+
93
+ Set `once={true}` to disconnect the observer after the element becomes visible for the first time. Perfect for entrance animations that should only play once.
94
+
95
+ ```svelte
96
+ <script>
97
+ import { Sentinel } from '@ariefsn/svelte-sentinel';
98
+ </script>
99
+
100
+ <style>
101
+ .box { opacity: 0; transform: translateY(2rem); transition: opacity .5s, transform .5s; }
102
+ .box.visible { opacity: 1; transform: none; }
103
+ </style>
104
+
105
+ <Sentinel once>
106
+ {#snippet whenVisible()}
107
+ <div class="box visible">I animated in!</div>
108
+ {/snippet}
109
+ {#snippet whenHidden()}
110
+ <div class="box">Waiting…</div>
111
+ {/snippet}
112
+ </Sentinel>
113
+ ```
114
+
115
+ ---
116
+
117
+ ### Conditional content with `whenVisible` / `whenHidden`
118
+
119
+ Use the `whenVisible` and `whenHidden` snippet slots to declaratively swap content based on viewport position. No state management required in the parent.
120
+
121
+ ```svelte
122
+ <Sentinel>
123
+ {#snippet whenVisible()}
124
+ <span class="badge green">In view</span>
125
+ {/snippet}
126
+ {#snippet whenHidden()}
127
+ <span class="badge grey">Out of view</span>
128
+ {/snippet}
129
+ </Sentinel>
130
+ ```
131
+
132
+ > **Note:** `children` is always rendered and is independent of `whenVisible` / `whenHidden`. Use `children` for static content that should always be inside the wrapper.
133
+
134
+ ---
135
+
136
+ ### Lazy loading with `rootMargin`
137
+
138
+ `rootMargin` expands the detection area beyond the viewport — useful for pre-loading images or data before they scroll into view.
139
+
140
+ ```svelte
141
+ <script>
142
+ import { Sentinel } from '@ariefsn/svelte-sentinel';
143
+ let loaded = $state(false);
144
+ </script>
145
+
146
+ <!-- Start loading 300px before the image enters the viewport -->
147
+ <Sentinel rootMargin="300px" once onEnter={() => (loaded = true)}>
148
+ {#if loaded}
149
+ <img src="/photo.jpg" alt="Lazy loaded" />
150
+ {:else}
151
+ <div class="placeholder">Loading…</div>
152
+ {/if}
153
+ </Sentinel>
154
+ ```
155
+
156
+ ---
157
+
158
+ ### Custom scroll container with `root`
159
+
160
+ Observe visibility inside a scrollable container instead of the browser viewport.
161
+
162
+ ```svelte
163
+ <script>
164
+ import { Sentinel } from '@ariefsn/svelte-sentinel';
165
+ let container: HTMLElement;
166
+ </script>
167
+
168
+ <div bind:this={container} style="overflow-y: scroll; height: 400px;">
169
+ <Sentinel root={container} onEnter={() => console.log('visible inside container')}>
170
+ <div style="margin-top: 600px;">Deep content</div>
171
+ </Sentinel>
172
+ </div>
173
+ ```
174
+
175
+ ---
176
+
177
+ ### Custom wrapper tag with `as`
178
+
179
+ Change the wrapper element's HTML tag to fit your semantic HTML.
180
+
181
+ ```svelte
182
+ <Sentinel as="section" class="hero-section" onEnter={handleEnter}>
183
+ <h1>Hero Section</h1>
184
+ </Sentinel>
185
+
186
+ <!-- Renders as: <section class="hero-section">…</section> -->
187
+ ```
188
+
189
+ ---
190
+
191
+ ### Infinite scroll
192
+
193
+ Use a small invisible sentinel at the end of a list to trigger loading the next page.
194
+
195
+ ```svelte
196
+ <script>
197
+ import { Sentinel } from '@ariefsn/svelte-sentinel';
198
+
199
+ let items = $state(Array.from({ length: 20 }, (_, i) => i + 1));
200
+ let loading = $state(false);
201
+
202
+ async function loadMore() {
203
+ if (loading) return;
204
+ loading = true;
205
+ await new Promise((r) => setTimeout(r, 500)); // simulate fetch
206
+ items = [...items, ...Array.from({ length: 20 }, (_, i) => items.length + i + 1)];
207
+ loading = false;
208
+ }
209
+ </script>
210
+
211
+ <ul>
212
+ {#each items as item}
213
+ <li>{item}</li>
214
+ {/each}
215
+ </ul>
216
+
217
+ {#if loading}
218
+ <p>Loading…</p>
219
+ {/if}
220
+
221
+ <!-- Invisible sentinel at the bottom of the list -->
222
+ <Sentinel onEnter={loadMore} />
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Props
228
+
229
+ | Prop | Type | Default | Description |
230
+ |------|------|---------|-------------|
231
+ | `id` | `string` | auto-generated | `id` attribute on the wrapper element. |
232
+ | `class` | `string` | `undefined` | CSS class(es) for the wrapper element. |
233
+ | `as` | `string` | `'div'` | HTML tag for the wrapper element (e.g. `'section'`, `'li'`). |
234
+ | `threshold` | `number \| number[]` | `0` | When to trigger: `0` = any pixel visible, `1` = fully visible, or an array of ratios. |
235
+ | `rootMargin` | `string` | `'0px'` | CSS margin around the root. Positive values expand, negative values shrink the detection area. |
236
+ | `root` | `Element \| Document \| null` | `null` | Scroll container to use instead of the browser viewport. |
237
+ | `once` | `boolean` | `false` | Disconnect the observer after the element first becomes visible. |
238
+ | `isVisible` | `boolean` | `false` | Bindable. Use `bind:isVisible` to reactively track visibility. |
239
+ | `onEnter` | `(entry: IntersectionObserverEntry) => void` | — | Called when the element enters the viewport. |
240
+ | `onExit` | `(entry: IntersectionObserverEntry) => void` | — | Called when the element exits the viewport. |
241
+ | `onViewChange` | `(isVisible: boolean, entry: IntersectionObserverEntry) => void` | — | Called on every visibility change. |
242
+ | `children` | `Snippet` | — | Always-rendered content inside the wrapper. |
243
+ | `whenVisible` | `Snippet` | — | Rendered only while the element is visible. |
244
+ | `whenHidden` | `Snippet` | — | Rendered only while the element is not visible. |
245
+
246
+ ---
247
+
248
+ ## TypeScript
249
+
250
+ All types are exported from the package entry point.
251
+
252
+ ```ts
253
+ import type { SentinelProps } from '@ariefsn/svelte-sentinel';
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Developing
259
+
260
+ ```bash
261
+ # Install dependencies
262
+ bun install
263
+
264
+ # Start the dev server (runs the demo app)
265
+ bun run dev
266
+
267
+ # Run tests
268
+ bun run test
269
+
270
+ # Type-check
271
+ bun run check
272
+
273
+ # Build the library
274
+ bun run prepack
275
+ ```
276
+
277
+ ---
278
+
279
+ ## License
280
+
281
+ [MIT](./LICENSE.md) © [ariefsn](https://github.com/ariefsn)
282
+
283
+ ---
284
+
285
+ [GitHub](https://github.com/ariefsn/svelte-sentinel) · [npm](https://www.npmjs.com/package/@ariefsn/svelte-sentinel)
@@ -0,0 +1,182 @@
1
+ <script lang="ts">
2
+ import { onMount, type Snippet } from 'svelte';
3
+
4
+ /**
5
+ * Props for the Sentinel component.
6
+ *
7
+ * Sentinel wraps the browser's `IntersectionObserver` API into a reactive
8
+ * Svelte 5 component, letting you declaratively respond to an element
9
+ * entering or leaving the viewport (or a custom scroll container).
10
+ */
11
+ export type SentinelProps = {
12
+ /**
13
+ * Unique `id` for the wrapper element.
14
+ * If omitted, a random ID is auto-generated.
15
+ */
16
+ id?: string;
17
+
18
+ /**
19
+ * CSS class(es) to apply to the wrapper element.
20
+ */
21
+ class?: string;
22
+
23
+ /**
24
+ * HTML tag to use as the wrapper element.
25
+ * @default 'div'
26
+ * @example 'section' | 'article' | 'span' | 'li'
27
+ */
28
+ as?: string;
29
+
30
+ /**
31
+ * Called when the element **enters** the viewport (or root).
32
+ * Receives the raw `IntersectionObserverEntry` for advanced usage
33
+ * (e.g. reading `intersectionRatio`).
34
+ */
35
+ onEnter?: (entry: IntersectionObserverEntry) => void;
36
+
37
+ /**
38
+ * Called when the element **exits** the viewport (or root).
39
+ * Receives the raw `IntersectionObserverEntry`.
40
+ */
41
+ onExit?: (entry: IntersectionObserverEntry) => void;
42
+
43
+ /**
44
+ * Called whenever the element's visibility changes (enter **or** exit).
45
+ * @param isVisible - `true` if entering, `false` if exiting.
46
+ * @param entry - The raw `IntersectionObserverEntry`.
47
+ */
48
+ onViewChange?: (isVisible: boolean, entry: IntersectionObserverEntry) => void;
49
+
50
+ /**
51
+ * `IntersectionObserver` threshold(s). A number or array of numbers
52
+ * between `0` and `1`.
53
+ * - `0` — fires as soon as **any** pixel is visible (default).
54
+ * - `1` — fires only when the element is **fully** visible.
55
+ * - `[0, 0.5, 1]` — fires at 0 %, 50 %, and 100 % visibility.
56
+ * @default 0
57
+ */
58
+ threshold?: number | number[];
59
+
60
+ /**
61
+ * Margin around the root element, using CSS shorthand syntax
62
+ * (e.g. `'100px 0px'`).
63
+ * - Positive values **expand** the detection area (useful for
64
+ * pre-loading content before it scrolls into view).
65
+ * - Negative values **shrink** the detection area.
66
+ * @default '0px'
67
+ */
68
+ rootMargin?: string;
69
+
70
+ /**
71
+ * The element used as the intersection viewport.
72
+ * - Set to a scrollable container element to observe visibility
73
+ * within that container instead of the browser viewport.
74
+ * - `null` uses the browser viewport.
75
+ * @default null
76
+ */
77
+ root?: Element | Document | null;
78
+
79
+ /**
80
+ * When `true`, the observer automatically disconnects after the
81
+ * element becomes visible for the **first time**. Ideal for
82
+ * one-time animations or lazy-loading.
83
+ * @default false
84
+ */
85
+ once?: boolean;
86
+
87
+ /**
88
+ * Reactive flag indicating whether the element is currently visible.
89
+ * Use `bind:isVisible` on the parent to read this value reactively
90
+ * without needing a callback.
91
+ * @default false
92
+ */
93
+ isVisible?: boolean;
94
+
95
+ /**
96
+ * Content that is **always** rendered inside the sentinel wrapper,
97
+ * regardless of visibility.
98
+ */
99
+ children?: Snippet;
100
+
101
+ /**
102
+ * Content rendered **only while the element is visible**.
103
+ * Swaps with `whenHidden` as the element enters/exits the viewport.
104
+ */
105
+ whenVisible?: Snippet;
106
+
107
+ /**
108
+ * Content rendered **only while the element is not visible**.
109
+ * Swaps with `whenVisible` as the element enters/exits the viewport.
110
+ */
111
+ whenHidden?: Snippet;
112
+ };
113
+
114
+ let {
115
+ id,
116
+ class: className,
117
+ as: tag = 'div',
118
+ onEnter,
119
+ onExit,
120
+ onViewChange,
121
+ threshold = 0,
122
+ rootMargin = '0px',
123
+ root = null,
124
+ once = false,
125
+ isVisible = $bindable(false),
126
+ children,
127
+ whenVisible,
128
+ whenHidden
129
+ }: SentinelProps = $props();
130
+
131
+ let ref: Element | null = $state(null);
132
+
133
+ onMount(() => {
134
+ if (!ref) return;
135
+
136
+ const observer = new IntersectionObserver(
137
+ (entries) => {
138
+ const entry = entries[0];
139
+ if (!entry) return;
140
+
141
+ const visible = entry.isIntersecting;
142
+ isVisible = visible;
143
+ onViewChange?.(visible, entry);
144
+
145
+ if (visible) {
146
+ onEnter?.(entry);
147
+ if (once) observer.disconnect();
148
+ } else {
149
+ onExit?.(entry);
150
+ }
151
+ },
152
+ { threshold, rootMargin, root }
153
+ );
154
+
155
+ observer.observe(ref);
156
+ return () => observer.disconnect();
157
+ });
158
+ </script>
159
+
160
+ <!--
161
+ @component
162
+ **Sentinel** — a lightweight Svelte 5 wrapper around `IntersectionObserver`.
163
+
164
+ Detects when the wrapped element enters or exits the viewport and exposes
165
+ callbacks, a bindable `isVisible` flag, and conditional snippet slots
166
+ (`whenVisible` / `whenHidden`).
167
+
168
+ @example
169
+ ```svelte
170
+ <Sentinel onEnter={() => console.log('visible!')} onExit={() => console.log('hidden!')}>
171
+ <p>Watch me scroll!</p>
172
+ </Sentinel>
173
+ ```
174
+ -->
175
+ <svelte:element this={tag} {id} class={className} bind:this={ref}>
176
+ {@render children?.()}
177
+ {#if isVisible}
178
+ {@render whenVisible?.()}
179
+ {:else}
180
+ {@render whenHidden?.()}
181
+ {/if}
182
+ </svelte:element>
@@ -0,0 +1,114 @@
1
+ import { type Snippet } from 'svelte';
2
+ /**
3
+ * Props for the Sentinel component.
4
+ *
5
+ * Sentinel wraps the browser's `IntersectionObserver` API into a reactive
6
+ * Svelte 5 component, letting you declaratively respond to an element
7
+ * entering or leaving the viewport (or a custom scroll container).
8
+ */
9
+ export type SentinelProps = {
10
+ /**
11
+ * Unique `id` for the wrapper element.
12
+ * If omitted, a random ID is auto-generated.
13
+ */
14
+ id?: string;
15
+ /**
16
+ * CSS class(es) to apply to the wrapper element.
17
+ */
18
+ class?: string;
19
+ /**
20
+ * HTML tag to use as the wrapper element.
21
+ * @default 'div'
22
+ * @example 'section' | 'article' | 'span' | 'li'
23
+ */
24
+ as?: string;
25
+ /**
26
+ * Called when the element **enters** the viewport (or root).
27
+ * Receives the raw `IntersectionObserverEntry` for advanced usage
28
+ * (e.g. reading `intersectionRatio`).
29
+ */
30
+ onEnter?: (entry: IntersectionObserverEntry) => void;
31
+ /**
32
+ * Called when the element **exits** the viewport (or root).
33
+ * Receives the raw `IntersectionObserverEntry`.
34
+ */
35
+ onExit?: (entry: IntersectionObserverEntry) => void;
36
+ /**
37
+ * Called whenever the element's visibility changes (enter **or** exit).
38
+ * @param isVisible - `true` if entering, `false` if exiting.
39
+ * @param entry - The raw `IntersectionObserverEntry`.
40
+ */
41
+ onViewChange?: (isVisible: boolean, entry: IntersectionObserverEntry) => void;
42
+ /**
43
+ * `IntersectionObserver` threshold(s). A number or array of numbers
44
+ * between `0` and `1`.
45
+ * - `0` — fires as soon as **any** pixel is visible (default).
46
+ * - `1` — fires only when the element is **fully** visible.
47
+ * - `[0, 0.5, 1]` — fires at 0 %, 50 %, and 100 % visibility.
48
+ * @default 0
49
+ */
50
+ threshold?: number | number[];
51
+ /**
52
+ * Margin around the root element, using CSS shorthand syntax
53
+ * (e.g. `'100px 0px'`).
54
+ * - Positive values **expand** the detection area (useful for
55
+ * pre-loading content before it scrolls into view).
56
+ * - Negative values **shrink** the detection area.
57
+ * @default '0px'
58
+ */
59
+ rootMargin?: string;
60
+ /**
61
+ * The element used as the intersection viewport.
62
+ * - Set to a scrollable container element to observe visibility
63
+ * within that container instead of the browser viewport.
64
+ * - `null` uses the browser viewport.
65
+ * @default null
66
+ */
67
+ root?: Element | Document | null;
68
+ /**
69
+ * When `true`, the observer automatically disconnects after the
70
+ * element becomes visible for the **first time**. Ideal for
71
+ * one-time animations or lazy-loading.
72
+ * @default false
73
+ */
74
+ once?: boolean;
75
+ /**
76
+ * Reactive flag indicating whether the element is currently visible.
77
+ * Use `bind:isVisible` on the parent to read this value reactively
78
+ * without needing a callback.
79
+ * @default false
80
+ */
81
+ isVisible?: boolean;
82
+ /**
83
+ * Content that is **always** rendered inside the sentinel wrapper,
84
+ * regardless of visibility.
85
+ */
86
+ children?: Snippet;
87
+ /**
88
+ * Content rendered **only while the element is visible**.
89
+ * Swaps with `whenHidden` as the element enters/exits the viewport.
90
+ */
91
+ whenVisible?: Snippet;
92
+ /**
93
+ * Content rendered **only while the element is not visible**.
94
+ * Swaps with `whenVisible` as the element enters/exits the viewport.
95
+ */
96
+ whenHidden?: Snippet;
97
+ };
98
+ /**
99
+ * **Sentinel** — a lightweight Svelte 5 wrapper around `IntersectionObserver`.
100
+ *
101
+ * Detects when the wrapped element enters or exits the viewport and exposes
102
+ * callbacks, a bindable `isVisible` flag, and conditional snippet slots
103
+ * (`whenVisible` / `whenHidden`).
104
+ *
105
+ * @example
106
+ * ```svelte
107
+ * <Sentinel onEnter={() => console.log('visible!')} onExit={() => console.log('hidden!')}>
108
+ * <p>Watch me scroll!</p>
109
+ * </Sentinel>
110
+ * ```
111
+ */
112
+ declare const Sentinel: import("svelte").Component<SentinelProps, {}, "isVisible">;
113
+ type Sentinel = ReturnType<typeof Sentinel>;
114
+ export default Sentinel;
@@ -0,0 +1,2 @@
1
+ export { default as Sentinel } from './components/Sentinel.svelte';
2
+ export type { SentinelProps } from './components/Sentinel.svelte';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { default as Sentinel } from './components/Sentinel.svelte';
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@ariefsn/svelte-sentinel",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight Svelte 5 component that wraps IntersectionObserver for declarative viewport visibility detection.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ariefsn/svelte-sentinel.git"
9
+ },
10
+ "homepage": "https://github.com/ariefsn/svelte-sentinel#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/ariefsn/svelte-sentinel/issues"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "scripts": {
16
+ "dev": "vite dev",
17
+ "build": "vite build && bun run prepack",
18
+ "preview": "vite preview",
19
+ "prepare": "svelte-kit sync || echo ''",
20
+ "prepack": "svelte-kit sync && svelte-package && publint",
21
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
22
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
23
+ "lint": "prettier --check . && eslint .",
24
+ "format": "prettier --write .",
25
+ "test:unit": "vitest",
26
+ "test": "bun run test:unit -- --run"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "!dist/**/*.test.*",
31
+ "!dist/**/*.spec.*"
32
+ ],
33
+ "sideEffects": [
34
+ "**/*.css"
35
+ ],
36
+ "svelte": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "type": "module",
39
+ "exports": {
40
+ ".": {
41
+ "types": "./dist/index.d.ts",
42
+ "svelte": "./dist/index.js"
43
+ }
44
+ },
45
+ "peerDependencies": {
46
+ "svelte": "^5.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@eslint/compat": "^2.0.2",
50
+ "@eslint/js": "^9.39.2",
51
+ "@sveltejs/adapter-auto": "^7.0.0",
52
+ "@sveltejs/kit": "^2.50.2",
53
+ "@sveltejs/package": "^2.5.7",
54
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
55
+ "@types/node": "^22",
56
+ "@vitest/browser-playwright": "^4.0.18",
57
+ "eslint": "^9.39.2",
58
+ "eslint-config-prettier": "^10.1.8",
59
+ "eslint-plugin-svelte": "^3.14.0",
60
+ "globals": "^17.3.0",
61
+ "playwright": "^1.58.1",
62
+ "prettier": "^3.8.1",
63
+ "prettier-plugin-svelte": "^3.4.1",
64
+ "publint": "^0.3.17",
65
+ "svelte": "^5.51.0",
66
+ "svelte-check": "^4.3.6",
67
+ "typescript": "^5.9.3",
68
+ "typescript-eslint": "^8.54.0",
69
+ "vite": "^7.3.1",
70
+ "vitest": "^4.0.18",
71
+ "vitest-browser-svelte": "^2.0.2"
72
+ },
73
+ "keywords": [
74
+ "svelte",
75
+ "svelte5",
76
+ "intersection-observer",
77
+ "visibility",
78
+ "viewport",
79
+ "scroll",
80
+ "lazy-load",
81
+ "animate-on-scroll",
82
+ "in-view",
83
+ "sentinel"
84
+ ]
85
+ }