svelte-intersection-observer 1.2.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,13 +1,29 @@
1
1
  # svelte-intersection-observer
2
2
 
3
+ <!-- HIDE_START -->
3
4
  [![NPM][npm]][npm-url]
5
+ <!-- HIDE_END -->
4
6
 
5
7
  > Detect if an element is in the viewport using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
6
8
 
7
- <!-- REPO_URL -->
9
+ Use it to lazy-load images, trigger scroll animations, implement infinite scroll, autoplay video when visible, track ad or analytics impressions, or detect when a user has scrolled to the end of a list.
10
+
11
+ This zero-dependency library offers several ways to observe elements:
12
+
13
+ - `IntersectionObserver`: component for observing a single element
14
+ - `MultipleIntersectionObserver`: component for observing multiple elements (shared observer for better performance)
15
+ - `intersect`: action for observing an element directly with `use:`
16
+ - `intersectAttachment`: attachment for observing an element directly with `{@attach}` (Svelte 5.29+)
8
17
 
9
18
  Try it in the [Svelte REPL](https://svelte.dev/repl/8cd2327a580c4f429c71f7df999bd51d).
10
19
 
20
+ ## Compatibility
21
+
22
+ | Package version | Svelte version | Notes |
23
+ | :--------------- | :----------------- | :----------------------------------------- |
24
+ | 1.x | 3, 4, 5 (non-runes) | Uses `export let`, slots, and `on:` events |
25
+ | 2.x | 5 (runes mode only) | Uses `$props()`, snippets, and callback props |
26
+
11
27
  <!-- TOC -->
12
28
 
13
29
  ## Installation
@@ -39,8 +55,8 @@ Then, simply bind to the reactive `intersecting` prop to determine if the elemen
39
55
  <script>
40
56
  import IntersectionObserver from "svelte-intersection-observer";
41
57
 
42
- let element;
43
- let intersecting;
58
+ let element = $state();
59
+ let intersecting = $state(false);
44
60
  </script>
45
61
 
46
62
  <header class:intersecting>
@@ -52,6 +68,33 @@ Then, simply bind to the reactive `intersecting` prop to determine if the elemen
52
68
  </IntersectionObserver>
53
69
  ```
54
70
 
71
+ ### `children` snippet
72
+
73
+ An alternative to binding to the `intersecting` prop is to use the `children` snippet, which receives `intersecting`, `entry`, and `observer`.
74
+
75
+ In the following example, the "Hello world" element fades in when its containing element intersects the viewport.
76
+
77
+ ```svelte
78
+ <script>
79
+ import IntersectionObserver from "svelte-intersection-observer";
80
+ import { fade } from "svelte/transition";
81
+
82
+ let node = $state();
83
+ </script>
84
+
85
+ <header></header>
86
+
87
+ <IntersectionObserver element={node}>
88
+ {#snippet children({ intersecting })}
89
+ <div bind:this={node}>
90
+ {#if intersecting}
91
+ <div transition:fade={{ delay: 200 }}>Hello world</div>
92
+ {/if}
93
+ </div>
94
+ {/snippet}
95
+ </IntersectionObserver>
96
+ ```
97
+
55
98
  ### Once
56
99
 
57
100
  Set `once` to `true` for the intersection event to occur only once. The `element` will be unobserved after the first intersection event occurs.
@@ -60,8 +103,8 @@ Set `once` to `true` for the intersection event to occur only once. The `element
60
103
  <script>
61
104
  import IntersectionObserver from "svelte-intersection-observer";
62
105
 
63
- let elementOnce;
64
- let intersectOnce;
106
+ let elementOnce = $state();
107
+ let intersectOnce = $state(false);
65
108
  </script>
66
109
 
67
110
  <header class:intersecting={intersectOnce}>
@@ -79,78 +122,55 @@ Set `once` to `true` for the intersection event to occur only once. The `element
79
122
 
80
123
  ### Pausing with `skip`
81
124
 
82
- Set `skip` to `true` to unobserve without disconnecting the underlying observer or losing `entry`/`intersecting` state useful for pausing tracking on an off-screen carousel panel or a closed modal. Set `skip` back to `false` to resume; unlike `once`, this can be toggled back and forth. `MultipleIntersectionObserver` and the `intersect` action support the same `skip` option.
125
+ Set `skip` to `true` to unobserve without disconnecting the underlying observer or losing `entry`/`intersecting` state. This is useful for pausing tracking on an off-screen carousel panel or a closed modal. Set `skip` back to `false` to resume; unlike `once`, this can be toggled back and forth. `MultipleIntersectionObserver` and the `intersect` action support the same `skip` option.
83
126
 
84
127
  ```svelte no-eval
85
128
  <script>
86
129
  import IntersectionObserver from "svelte-intersection-observer";
87
130
 
88
- let elementSkip;
89
- let paused = false;
131
+ let elementSkip = $state();
132
+ let paused = $state(false);
90
133
  </script>
91
134
 
92
- <button on:click={() => (paused = !paused)}>
135
+ <button onclick={() => (paused = !paused)}>
93
136
  {paused ? "Resume" : "Pause"}
94
137
  </button>
95
138
 
96
- <IntersectionObserver element={elementSkip} skip={paused} let:intersecting>
97
- <div bind:this={elementSkip}>{intersecting ? "In view" : "Not in view"}</div>
139
+ <IntersectionObserver element={elementSkip} skip={paused}>
140
+ {#snippet children({ intersecting })}
141
+ <div bind:this={elementSkip}>{intersecting ? "In view" : "Not in view"}</div>
142
+ {/snippet}
98
143
  </IntersectionObserver>
99
144
  ```
100
145
 
101
- ### `let:intersecting`
102
-
103
- An alternative to binding to the `intersecting` prop is to use the `let:` directive.
146
+ ### `onobserve` callback prop
104
147
 
105
- In the following example, the "Hello world" element will fade in when its containing element intersects the viewport.
106
-
107
- ```svelte
108
- <script>
109
- import IntersectionObserver from "svelte-intersection-observer";
110
- import { fade } from "svelte/transition";
111
-
112
- let node;
113
- </script>
114
-
115
- <header></header>
116
-
117
- <IntersectionObserver element={node} let:intersecting>
118
- <div bind:this={node}>
119
- {#if intersecting}
120
- <div transition:fade={{ delay: 200 }}>Hello world</div>
121
- {/if}
122
- </div>
123
- </IntersectionObserver>
124
- ```
125
-
126
- ### `on:observe` event
127
-
128
- The `observe` event is dispatched when the element is first observed and also whenever an intersection event occurs.
148
+ `onobserve` is called when the element is first observed and also whenever an intersection event occurs.
129
149
 
130
150
  ```svelte no-eval
131
151
  <IntersectionObserver
132
152
  {element}
133
- on:observe={(e) => {
134
- console.log(e.detail); // IntersectionObserverEntry
135
- console.log(e.detail.isIntersecting); // true | false
153
+ onobserve={(entry) => {
154
+ console.log(entry); // IntersectionObserverEntry
155
+ console.log(entry.isIntersecting); // true | false
136
156
  }}
137
157
  >
138
158
  <div bind:this={element}>Hello world</div>
139
159
  </IntersectionObserver>
140
160
  ```
141
161
 
142
- ### `on:intersect` event
162
+ ### `onintersect` callback prop
143
163
 
144
- As an alternative to binding the `intersecting` prop, you can listen to the `intersect` event that is dispatched if the observed element is intersecting the viewport.
164
+ As an alternative to binding the `intersecting` prop, you can pass an `onintersect` callback that is called if the observed element is intersecting the viewport.
145
165
 
146
- **Note**: Compared to `on:observe`, `on:intersect` is dispatched only when the element is _intersecting the viewport_. In other words, `e.detail.isIntersecting` will only be `true`.
166
+ **Note**: Compared to `onobserve`, `onintersect` is called only when the element is _intersecting the viewport_. In other words, `entry.isIntersecting` will only be `true`.
147
167
 
148
168
  ```svelte no-eval
149
169
  <IntersectionObserver
150
170
  {element}
151
- on:intersect={(e) => {
152
- console.log(e.detail); // IntersectionObserverEntry
153
- console.log(e.detail.isIntersecting); // true
171
+ onintersect={(entry) => {
172
+ console.log(entry); // IntersectionObserverEntry
173
+ console.log(entry.isIntersecting); // true
154
174
  }}
155
175
  >
156
176
  <div bind:this={element}>Hello world</div>
@@ -167,9 +187,9 @@ To detect when a user has scrolled to the end of a scrollable container, place a
167
187
  <script>
168
188
  import IntersectionObserver from "svelte-intersection-observer";
169
189
 
170
- let container;
171
- let sentinel;
172
- let reachedEnd;
190
+ let container = $state();
191
+ let sentinel = $state();
192
+ let reachedEnd = $state(false);
173
193
  </script>
174
194
 
175
195
  <header class:intersecting={reachedEnd}>
@@ -191,15 +211,49 @@ To detect when a user has scrolled to the end of a scrollable container, place a
191
211
  </div>
192
212
  ```
193
213
 
214
+ ### Scrolling to a conditionally revealed element
215
+
216
+ Keep the `bind:this` element outside the `{#if intersecting}` block, and only gate the animated content inside it. The bound element then stays in the DOM, so you can call `scrollIntoView()` on it whenever you want, whether or not its reveal animation has played yet.
217
+
218
+ ```svelte
219
+ <script>
220
+ import IntersectionObserver from "svelte-intersection-observer";
221
+ import { fly } from "svelte/transition";
222
+
223
+ let revealNode = $state();
224
+ let revealIntersecting = $state(false);
225
+ </script>
226
+
227
+ <header class:intersecting={revealIntersecting}>
228
+ <button
229
+ onclick={() => {
230
+ revealNode.scrollIntoView({ behavior: "smooth", block: "nearest" });
231
+ }}
232
+ >
233
+ Scroll to section
234
+ </button>
235
+ </header>
236
+
237
+ <IntersectionObserver element={revealNode} once bind:intersecting={revealIntersecting}>
238
+ <div bind:this={revealNode}>
239
+ {#if revealIntersecting}
240
+ <section transition:fly={{ y: 50, delay: 200, duration: 300 }}>
241
+ Hello world
242
+ </section>
243
+ {/if}
244
+ </div>
245
+ </IntersectionObserver>
246
+ ```
247
+
194
248
  ### `use:intersect` action
195
249
 
196
- As an alternative to the `IntersectionObserver` component, use the `intersect` action to observe an element directly with `use:`, without a `bind:this` reference or wrapper markup. Listen for `on:observe`/`on:intersect` on the observed element itself.
250
+ As an alternative to the `IntersectionObserver` component, use the `intersect` action to observe an element directly with `use:`, without a `bind:this` reference or wrapper markup. Listen for `onobserve`/`onintersect` on the observed element itself.
197
251
 
198
252
  ```svelte
199
253
  <script>
200
254
  import { intersect } from "svelte-intersection-observer";
201
255
 
202
- let actionIntersecting = false;
256
+ let actionIntersecting = $state(false);
203
257
  </script>
204
258
 
205
259
  <header class:intersecting={actionIntersecting}>
@@ -208,92 +262,123 @@ As an alternative to the `IntersectionObserver` component, use the `intersect` a
208
262
 
209
263
  <div
210
264
  use:intersect={{ once: true }}
211
- on:observe={(e) => (actionIntersecting = e.detail.isIntersecting)}
265
+ onobserve={(e) => (actionIntersecting = e.detail.isIntersecting)}
212
266
  >
213
267
  Hello world
214
268
  </div>
215
269
  ```
216
270
 
217
- Options passed to `use:intersect` are reactive updating `root`, `rootMargin`, or `threshold` re-initializes the underlying observer. Updating `skip` toggles observing on the existing observer without re-initializing it.
271
+ Options passed to `use:intersect` are reactive: updating `root`, `rootMargin`, or `threshold` re-initializes the underlying observer. Updating `skip` toggles observing on the existing observer without re-initializing it.
218
272
 
219
- ### Multiple elements
273
+ ### `intersectAttachment` attachment
274
+
275
+ As of Svelte 5.29, [attachments](https://svelte.dev/docs/svelte/svelte-attachments) are the preferred replacement for actions. `intersectAttachment` wraps the `intersect` action with `svelte/attachments`'s `fromAction`, reusing the same observer logic but plugging into `{@attach ...}` instead of `use:`.
276
+
277
+ Attachments have a few architectural advantages over actions:
278
+
279
+ - No separate `update()` lifecycle method; they rerun reactively like a `$effect`
280
+ - Just plain functions, so they're easier to compose and generate dynamically
281
+ - Can be forwarded through components as ordinary props, unlike actions
282
+
283
+ ```svelte
284
+ <script>
285
+ import { intersectAttachment } from "svelte-intersection-observer";
286
+
287
+ let attachmentIntersecting = $state(false);
288
+ </script>
289
+
290
+ <header class:intersecting={attachmentIntersecting}>
291
+ {attachmentIntersecting ? "Element is in view" : "Element is not in view"}
292
+ </header>
293
+
294
+ <div
295
+ {@attach intersectAttachment(() => ({ once: true }))}
296
+ onobserve={(e) => (attachmentIntersecting = e.detail.isIntersecting)}
297
+ >
298
+ Hello world
299
+ </div>
300
+ ```
220
301
 
221
- For performance, use `MultipleIntersectionObserver` to observe multiple elements.
302
+ **Note**: unlike `use:intersect`, which takes the options object directly, `intersectAttachment` takes a function that _returns_ the options object (this is how `fromAction` tracks reactive dependencies). `intersect` remains fully supported; use whichever fits your codebase.
222
303
 
223
- This avoids instantiating a new observer for every element.
304
+ ### Multiple elements
305
+
306
+ For performance, use `MultipleIntersectionObserver` to observe multiple elements with a single shared observer instead of instantiating a new one for every element.
224
307
 
225
308
  ```svelte
226
309
  <script>
227
310
  import { MultipleIntersectionObserver } from "svelte-intersection-observer";
228
311
 
229
- let ref1;
230
- let ref2;
312
+ let ref1 = $state();
313
+ let ref2 = $state();
231
314
 
232
- $: elements = [ref1, ref2];
315
+ let elements = $derived([ref1, ref2]);
233
316
  </script>
234
317
 
235
- <MultipleIntersectionObserver {elements} let:elementIntersections>
236
- <header>
237
- <div class:intersecting={elementIntersections.get(ref1)}>
238
- Item 1: {elementIntersections.get(ref1) ? "✓" : "✗"}
239
- </div>
240
- <div class:intersecting={elementIntersections.get(ref2)}>
241
- Item 2: {elementIntersections.get(ref2) ? "✓" : "✗"}
242
- </div>
243
- </header>
318
+ <MultipleIntersectionObserver {elements}>
319
+ {#snippet children({ elementIntersections })}
320
+ <header>
321
+ <div class:intersecting={elementIntersections.get(ref1)}>
322
+ Item 1: {elementIntersections.get(ref1) ? "✓" : "✗"}
323
+ </div>
324
+ <div class:intersecting={elementIntersections.get(ref2)}>
325
+ Item 2: {elementIntersections.get(ref2) ? "✓" : "✗"}
326
+ </div>
327
+ </header>
244
328
 
245
- <div bind:this={ref1}>Item 1</div>
246
- <div bind:this={ref2}>Item 2</div>
329
+ <div bind:this={ref1}>Item 1</div>
330
+ <div bind:this={ref2}>Item 2</div>
331
+ {/snippet}
247
332
  </MultipleIntersectionObserver>
248
333
  ```
249
334
 
250
335
  ### Using with `#each`
251
336
 
252
- `MultipleIntersectionObserver` also handles a dynamic, `#each`-driven list give every item its own slot in an array/object instead of one shared variable.
337
+ `MultipleIntersectionObserver` also handles a dynamic, `#each`-driven list: give every item its own slot in an array/object instead of one shared variable.
253
338
 
254
339
  ```svelte
255
340
  <script>
256
341
  import { MultipleIntersectionObserver } from "svelte-intersection-observer";
257
342
 
258
- let items = [
259
- { id: 1, text: "Item 1" },
260
- { id: 2, text: "Item 2" },
261
- { id: 3, text: "Item 3" },
262
- ];
343
+ let items = Array.from({ length: 10 }, (_, i) => ({
344
+ id: i + 1,
345
+ text: `Item ${i + 1}`,
346
+ }));
263
347
 
264
- let refs = [];
265
- let itemsContainer;
348
+ let refs = $state([]);
349
+ let itemsContainer = $state();
266
350
 
267
- $: itemElements = refs;
351
+ let itemElements = $derived(refs);
268
352
  </script>
269
353
 
270
- <MultipleIntersectionObserver
271
- elements={itemElements}
272
- root={itemsContainer}
273
- let:elementIntersections
274
- >
275
- <header>
276
- {#each items as item, i (item.id)}
277
- <div class:intersecting={elementIntersections.get(refs[i])}>
278
- {item.text}: {elementIntersections.get(refs[i]) ? "✓" : "✗"}
279
- </div>
280
- {/each}
281
- </header>
282
-
283
- <div bind:this={itemsContainer} style="height: 150px; overflow-y: auto;">
284
- {#each items as item, i (item.id)}
285
- <div
286
- bind:this={refs[i]}
287
- style="height: 100px; display: flex; align-items: center;"
288
- >
289
- {item.text}
290
- </div>
291
- {/each}
292
- </div>
354
+ <MultipleIntersectionObserver elements={itemElements} root={itemsContainer}>
355
+ {#snippet children({ elementIntersections })}
356
+ <header>
357
+ {#each items as item, i (item.id)}
358
+ <div class:intersecting={elementIntersections.get(refs[i])}>
359
+ {item.text}: {elementIntersections.get(refs[i]) ? "✓" : "✗"}
360
+ </div>
361
+ {/each}
362
+ </header>
363
+
364
+ <div
365
+ bind:this={itemsContainer}
366
+ style="height: 150px; overflow-y: auto; display: flex; flex-direction: column; gap: 1rem;"
367
+ >
368
+ {#each items as item, i (item.id)}
369
+ <div
370
+ bind:this={refs[i]}
371
+ style="height: 100px; display: flex; align-items: center; flex-shrink: 0;"
372
+ >
373
+ {item.text}
374
+ </div>
375
+ {/each}
376
+ </div>
377
+ {/snippet}
293
378
  </MultipleIntersectionObserver>
294
379
  ```
295
380
 
296
- As with the scroll-to-end example, `root` must be an element that scrolls on its own here, `itemsContainer` has an explicit `height` and `overflow-y: auto`.
381
+ As with the scroll-to-end example, `root` must be an element that scrolls on its own; here, `itemsContainer` has an explicit `height` and `overflow-y: auto`.
297
382
 
298
383
  **Avoid** using the single-element `IntersectionObserver` component inside an `#each` block with one variable shared across iterations (e.g. `let node;` declared outside the loop and bound via `bind:this={node}` inside it). Every iteration overwrites the same `node`, so each observer instance keeps re-observing a moving target, which can produce an infinite update loop. Use `MultipleIntersectionObserver` with a per-item ref, as shown above, instead.
299
384
 
@@ -315,16 +400,16 @@ As with the scroll-to-end example, `root` must be an element that scrolls on its
315
400
  | observer | `IntersectionObserver` instance | `null` or [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) | `null` |
316
401
  | skip | Pause observing without losing `entry`/`intersecting` state | `boolean` | `false` |
317
402
 
318
- **Note**: the observed `element` must render with a non-zero width and height for `threshold` values greater than `0` to have any effect this is a constraint of the underlying [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API), not something this component controls.
403
+ **Note**: the observed `element` must render with a non-zero width and height for `threshold` values greater than `0` to have any effect. This is a constraint of the underlying [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API), not something this component controls.
319
404
 
320
- #### Dispatched events
405
+ #### Callback props
321
406
 
322
- - **on:observe**: fired when the element is first observed or whenever an intersection change occurs
323
- - **on:intersect**: fired when the element is intersecting the viewport
407
+ - **onobserve**: called when the element is first observed or whenever an intersection change occurs
408
+ - **onintersect**: called when the element is intersecting the viewport
324
409
 
325
- The `e.detail` dispatched by the `observe` and `intersect` events is an [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) interface.
410
+ Both callbacks are called with an [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry).
326
411
 
327
- #### Slot props
412
+ #### `children` snippet props
328
413
 
329
414
  | Name | Type |
330
415
  | :----------- | :------------------------------------------------------------------------------------------------------------------ |
@@ -348,12 +433,12 @@ The `e.detail` dispatched by the `observe` and `intersect` events is an [`Inters
348
433
  | observer | `IntersectionObserver` instance | `null` or [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) | `null` |
349
434
  | skip | Pause observing all elements without losing state | `boolean` | `false` |
350
435
 
351
- #### Dispatched events
436
+ #### Callback props
352
437
 
353
- - **on:observe**: fired when an element is first observed or whenever an intersection change occurs
354
- - **on:intersect**: fired when an element is intersecting the viewport
438
+ - **onobserve**: called when an element is first observed or whenever an intersection change occurs
439
+ - **onintersect**: called when an element is intersecting the viewport
355
440
 
356
- The `e.detail` for both events includes:
441
+ Both callbacks are called with:
357
442
 
358
443
  ```ts
359
444
  {
@@ -362,7 +447,7 @@ The `e.detail` for both events includes:
362
447
  }
363
448
  ```
364
449
 
365
- #### Slot props
450
+ #### `children` snippet props
366
451
 
367
452
  | Name | Type |
368
453
  | :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
@@ -370,22 +455,24 @@ The `e.detail` for both events includes:
370
455
  | elementIntersections | `Map<HTMLElement \| null, boolean>` |
371
456
  | elementEntries | `Map<HTMLElement \| null,` [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)`>` |
372
457
 
373
- ### `intersect` action
458
+ ### `intersect` action / `intersectAttachment` attachment
459
+
460
+ `intersectAttachment` is a thin wrapper around the `intersect` action (see [above](#intersectattachment-attachment)), so both share the same options and dispatched events.
374
461
 
375
462
  #### Options
376
463
 
377
- | Name | Description | Type | Default value |
378
- | :--------- | :------------------------------------------------------------ | :------------------------------------------------------------------- | :------------- |
379
- | root | Containing element | `null` or `HTMLElement` | `null` |
380
- | rootMargin | Margin offset of the containing element | `string` | `"0px"` |
381
- | threshold | Percentage of element visibility to trigger an event | `number` between 0 and 1, or an array of `number`s between 0 and 1 | `0` |
382
- | once | Unobserve the element after the first intersection event | `boolean` | `false` |
383
- | skip | Pause observing without disconnecting the observer | `boolean` | `false` |
464
+ | Name | Description | Type | Default value |
465
+ | :--------- | :------------------------------------------------------- | :----------------------------------------------------------------- | :------------ |
466
+ | root | Containing element | `null` or `HTMLElement` | `null` |
467
+ | rootMargin | Margin offset of the containing element | `string` | `"0px"` |
468
+ | threshold | Percentage of element visibility to trigger an event | `number` between 0 and 1, or an array of `number`s between 0 and 1 | `0` |
469
+ | once | Unobserve the element after the first intersection event | `boolean` | `false` |
470
+ | skip | Pause observing without disconnecting the observer | `boolean` | `false` |
384
471
 
385
472
  #### Dispatched events
386
473
 
387
- - **on:observe**: fired on the element when it is first observed or whenever an intersection change occurs
388
- - **on:intersect**: fired on the element when it is intersecting the viewport
474
+ - **onobserve**: fired on the element when it is first observed or whenever an intersection change occurs
475
+ - **onintersect**: fired on the element when it is intersecting the viewport
389
476
 
390
477
  The `e.detail` dispatched by the `observe` and `intersect` events is an [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) interface.
391
478
 
@@ -438,13 +525,5 @@ interface IntersectionObserverEntry {
438
525
 
439
526
  </details>
440
527
 
441
- ## Changelog
442
-
443
- [Changelog](CHANGELOG.md)
444
-
445
- ## License
446
-
447
- [MIT](LICENSE)
448
-
449
528
  [npm]: https://img.shields.io/npm/v/svelte-intersection-observer.svg?color=%23ff3e00&style=for-the-badge
450
529
  [npm-url]: https://npmjs.com/package/svelte-intersection-observer
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { default } from "./IntersectionObserver.svelte";
2
- export type { IntersectActionOptions } from "./intersect.js";
3
- export { intersect } from "./intersect.js";
2
+ export type { IntersectActionOptions } from "./intersect.svelte.js";
3
+ export { intersect, intersectAttachment } from "./intersect.svelte.js";
4
4
  export { default as MultipleIntersectionObserver } from "./MultipleIntersectionObserver.svelte";
package/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { default } from "./IntersectionObserver.svelte";
2
- export { intersect } from "./intersect.js";
2
+ export { intersect, intersectAttachment } from "./intersect.svelte.js";
3
3
  export { default as MultipleIntersectionObserver } from "./MultipleIntersectionObserver.svelte";
@@ -1,4 +1,5 @@
1
1
  import type { Action } from "svelte/action";
2
+ import type { Attachment } from "svelte/attachments";
2
3
 
3
4
  export interface IntersectActionOptions {
4
5
  /**
@@ -39,15 +40,32 @@ export interface IntersectActionOptions {
39
40
  /**
40
41
  * Svelte action that observes the element with the Intersection Observer API.
41
42
  * Dispatches `observe` (on every change) and `intersect` (on entering the
42
- * viewport) `CustomEvent`s on the element — listen with `on:observe`/`on:intersect`.
43
+ * viewport) `CustomEvent`s on the element — listen with `onobserve`/`onintersect`.
43
44
  */
44
45
  export const intersect: Action<
45
46
  HTMLElement,
46
47
  IntersectActionOptions | undefined,
47
48
  {
48
- "on:observe"?: (event: CustomEvent<IntersectionObserverEntry>) => void;
49
- "on:intersect"?: (
49
+ onobserve?: (event: CustomEvent<IntersectionObserverEntry>) => void;
50
+ onintersect?: (
50
51
  event: CustomEvent<IntersectionObserverEntry & { isIntersecting: true }>,
51
52
  ) => void;
52
53
  }
53
54
  >;
55
+
56
+ /**
57
+ * Svelte attachment that observes the element with the Intersection Observer
58
+ * API. Equivalent to `intersect`, for use with `{@attach}` instead of `use:`.
59
+ */
60
+ export function intersectAttachment(
61
+ getOptions?: () => IntersectActionOptions,
62
+ ): Attachment<HTMLElement>;
63
+
64
+ declare module "svelte/elements" {
65
+ export interface HTMLAttributes<T> {
66
+ onobserve?: (event: CustomEvent<IntersectionObserverEntry>) => void;
67
+ onintersect?: (
68
+ event: CustomEvent<IntersectionObserverEntry & { isIntersecting: true }>,
69
+ ) => void;
70
+ }
71
+ }
@@ -1,9 +1,11 @@
1
+ import { fromAction } from "svelte/attachments";
2
+
1
3
  /**
2
4
  * Svelte action that observes `node` with the Intersection Observer API.
3
5
  * Dispatches `observe` (on every change) and `intersect` (on entering the
4
- * viewport) `CustomEvent`s on `node` — listen with `on:observe`/`on:intersect`.
6
+ * viewport) `CustomEvent`s on `node` — listen with `onobserve`/`onintersect`.
5
7
  * @param {HTMLElement} node
6
- * @param {import("./intersect.d.ts").IntersectActionOptions} [options]
8
+ * @param {import("./intersect.svelte.d.ts").IntersectActionOptions} [options]
7
9
  */
8
10
  export function intersect(node, options = {}) {
9
11
  let {
@@ -36,7 +38,7 @@ export function intersect(node, options = {}) {
36
38
  if (!skip) observer.observe(node);
37
39
 
38
40
  return {
39
- /** @param {import("./intersect.d.ts").IntersectActionOptions} [newOptions] */
41
+ /** @param {import("./intersect.svelte.d.ts").IntersectActionOptions} [newOptions] */
40
42
  update(newOptions = {}) {
41
43
  once = newOptions.once ?? false;
42
44
 
@@ -67,3 +69,13 @@ export function intersect(node, options = {}) {
67
69
  },
68
70
  };
69
71
  }
72
+
73
+ /**
74
+ * Svelte attachment that observes the element with the Intersection Observer
75
+ * API. Equivalent to the `intersect` action, for use with `{@attach}` instead
76
+ * of `use:`. Wraps `intersect` via `svelte/attachments`'s `fromAction`.
77
+ * @param {() => import("./intersect.svelte.d.ts").IntersectActionOptions} [getOptions]
78
+ */
79
+ export function intersectAttachment(getOptions = () => ({})) {
80
+ return fromAction(intersect, getOptions);
81
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-intersection-observer",
3
- "version": "1.2.0",
3
+ "version": "2.1.0",
4
4
  "license": "MIT",
5
5
  "description": "Detect if an element is in the viewport using the Intersection Observer API",
6
6
  "author": "Eric Liu (https://github.com/metonym)",