real-view 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/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # Real View 👁️
2
+
3
+ [![npm version](https://img.shields.io/npm/v/real-view.svg?style=flat-square)](https://www.npmjs.com/package/real-view)
4
+ [![minzipped size](https://img.shields.io/bundlephobia/minzip/real-view?style=flat-square)](https://bundlephobia.com/package/real-view)
5
+ [![license](https://img.shields.io/npm/l/real-view?style=flat-square)](LICENSE)
6
+
7
+ > **Stop guessing. Start knowing.**
8
+ > The only visibility tracker that knows if your user *actually* sees the element.
9
+
10
+ ## Why? 🤔
11
+
12
+ You use `IntersectionObserver` to track impressions. **You are lying to your analytics.**
13
+
14
+ Native observers fail in these common scenarios:
15
+ - ❌ **Occlusion:** A sticky header, modal, or dropdown covers the element.
16
+ - ❌ **Opacity:** The element is transparent (`opacity: 0`) or `visibility: hidden`.
17
+ - ❌ **Background Tabs:** The user switched tabs or minimized the browser.
18
+ - ❌ **Zero Size:** The element collapsed to 0x0 pixels.
19
+
20
+ **Real View** solves this. It combines `IntersectionObserver` with **DOM Raycasting**, **Computed Styles**, and **Page Visibility API** to guarantee physical visibility.
21
+
22
+ - **Universal:** First-class support for React, Vue, Svelte, Solid, Angular, and Vanilla.
23
+ - **Tiny:** ~1KB gzipped.
24
+ - **Smart:** Uses `requestIdleCallback` to prevent main-thread blocking.
25
+
26
+ ---
27
+
28
+ ## Installation 📦
29
+
30
+ ```bash
31
+ npm install real-view
32
+ # or
33
+ pnpm add real-view
34
+ # or
35
+ yarn add real-view
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Usage 🚀
41
+
42
+ ### React
43
+
44
+ Use the `useRealView` hook.
45
+
46
+ ```jsx
47
+ import { useEffect } from 'react'
48
+ import { useRealView } from 'real-view/react'
49
+
50
+ const AdBanner = () => {
51
+ const [ref, isVisible] = useRealView({ pollInterval: 1000 })
52
+
53
+ useEffect(() => {
54
+ if (isVisible) console.log("User is ACTUALLY looking at this!")
55
+ }, [isVisible])
56
+
57
+ return <div ref={ref}>Buy Now</div>
58
+ }
59
+
60
+ ```
61
+
62
+ ### Vue 3
63
+
64
+ Use the `useRealView` composable.
65
+
66
+ ```html
67
+ <script setup>
68
+ import { ref } from 'vue'
69
+ import { useRealView } from 'real-view/vue'
70
+
71
+ const el = ref(null)
72
+ const isVisible = useRealView(el)
73
+ </script>
74
+
75
+ <template>
76
+ <div ref="el">
77
+ Status: {{ isVisible ? 'SEEN' : 'HIDDEN' }}
78
+ </div>
79
+ </template>
80
+
81
+ ```
82
+
83
+ ### Svelte
84
+
85
+ Use the `realView` action.
86
+
87
+ ```svelte
88
+ <script>
89
+ import { realView } from 'real-view/svelte'
90
+ let visible = false;
91
+ </script>
92
+
93
+ <div use:realView={{ onUpdate: (v) => visible = v }}>
94
+ I am {visible ? 'visible' : 'hidden'}
95
+ </div>
96
+ ```
97
+
98
+ ### SolidJS
99
+
100
+ Use the `realView` directive.
101
+
102
+ ```tsx
103
+ iimport { createSignal } from 'solid-js';
104
+ import { realView } from 'real-view/solid';
105
+
106
+ // Typescript: declare module 'solid-js' { namespace JSX { interface Directives { realView: any; } } }
107
+
108
+ function App() {
109
+ const [visible, setVisible] = createSignal(false);
110
+
111
+ return (
112
+ <div use:realView={{ onUpdate: setVisible }}>
113
+ {visible() ? "I see you!" : "Where are you?"}
114
+ </div>
115
+ );
116
+ }
117
+
118
+ ```
119
+
120
+ ### Angular (14+)
121
+
122
+ Use the standalone `RealViewDirective`.
123
+
124
+ ```typescript
125
+ import { Component } from '@angular/core';
126
+ import { RealViewDirective } from 'real-view/angular';
127
+
128
+ @Component({
129
+ selector: 'app-tracker',
130
+ standalone: true,
131
+ imports: [RealViewDirective],
132
+ template: `
133
+ <div (realView)="onVisibilityChange($event)">
134
+ Track Me
135
+ </div>
136
+ `
137
+ })
138
+ export class TrackerComponent {
139
+ onVisibilityChange(isVisible: boolean) {
140
+ console.log('Visibility:', isVisible);
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### Vanilla JS
146
+
147
+ ```js
148
+ import { RealView } from 'real-view'
149
+
150
+ const el = document.querySelector('#banner')
151
+
152
+ const cleanup = RealView.observe(el, (isVisible) => {
153
+ console.log(isVisible ? 'Visible' : 'Hidden')
154
+ })
155
+
156
+ // Later
157
+ // cleanup()
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Configuration ⚙️
163
+
164
+ You can customize the strictness of the detection.
165
+
166
+ ```js
167
+ // React example
168
+ useRealView({
169
+ threshold: 0.5,
170
+ pollInterval: 500,
171
+ trackTab: true
172
+ })
173
+ ```
174
+ | Option | Type | Default | Description |
175
+ |---|---|---|---|
176
+ | `threshold` | `number` | `0` | How much of the element must be in viewport (0.0 - 1.0). |
177
+ | `pollInterval` | `number` | `1000` | How often (in ms) to check for occlusion (z-index). |
178
+ | `trackTab` | `boolean` | `true` | If `true`, reports `false` when user switches browser tabs. |
179
+
180
+ ---
181
+
182
+ ## How it works 🧊
183
+
184
+ Real View uses a **"Lazy Raycasting"** architecture to keep performance high:
185
+
186
+ 1. **Gatekeeper:** It uses `IntersectionObserver` first. If the element is off-screen, the CPU usage is **0%**.
187
+ 2. **Raycasting:** Once on-screen, it fires a ray (`document.elementFromPoint`) at the center of your element. If the ray hits a modal, a sticky header, or a dropdown menu instead of your element, visibility is `false`.
188
+ 3. **Style Audit:** It recursively checks `opacity`, `visibility`, and `display` up the DOM tree.
189
+ 4. **Tab Hygiene:** It listens to the Page Visibility API to pause tracking when the tab is backgrounded.
190
+
191
+ ## License
192
+
193
+ MIT
194
+
195
+ ## Keywords
196
+ `visibility` `viewport` `intersection` `occlusion` `tracking` `analytics` `impression` `react` `vue` `svelte` `angular` `solid` `dom` `monitor` `viewability`
197
+
@@ -0,0 +1,174 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var __decorateClass = (decorators, target, key, kind) => {
19
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
20
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
21
+ if (decorator = decorators[i])
22
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
23
+ if (kind && result) __defProp(target, key, result);
24
+ return result;
25
+ };
26
+
27
+ // src/angular.ts
28
+ var angular_exports = {};
29
+ __export(angular_exports, {
30
+ realViewDirective: () => realViewDirective
31
+ });
32
+ module.exports = __toCommonJS(angular_exports);
33
+ var import_core = require("@angular/core");
34
+
35
+ // src/core/utils.ts
36
+ var isBrowser = () => {
37
+ return typeof window !== "undefined" && typeof window.document !== "undefined";
38
+ };
39
+
40
+ // src/core/engine.ts
41
+ var realViewEngine = class _realViewEngine {
42
+ constructor() {
43
+ this.observer = null;
44
+ this.watchers = /* @__PURE__ */ new Map();
45
+ if (!isBrowser()) return;
46
+ this.initObserver();
47
+ this.initTabListener();
48
+ }
49
+ static get() {
50
+ if (!this.instance) this.instance = new _realViewEngine();
51
+ return this.instance;
52
+ }
53
+ initObserver() {
54
+ this.observer = new IntersectionObserver(this.handleIntersect.bind(this), {
55
+ threshold: 0
56
+ });
57
+ }
58
+ initTabListener() {
59
+ document.addEventListener("visibilitychange", () => {
60
+ const isHidden = document.hidden;
61
+ this.watchers.forEach((w) => {
62
+ if (w.opts.trackTab && isHidden) {
63
+ this.notify(w, false);
64
+ } else if (w.state.inViewport && !isHidden) {
65
+ this.checkOcclusion(w);
66
+ }
67
+ });
68
+ });
69
+ }
70
+ handleIntersect(entries) {
71
+ entries.forEach((entry) => {
72
+ const watcher = this.watchers.get(entry.target);
73
+ if (!watcher) return;
74
+ watcher.state.inViewport = entry.isIntersecting;
75
+ if (entry.isIntersecting) {
76
+ this.startPolling(watcher);
77
+ } else {
78
+ this.stopPolling(watcher);
79
+ this.notify(watcher, false);
80
+ }
81
+ });
82
+ }
83
+ checkOcclusion(w) {
84
+ if (w.opts.trackTab && document.hidden) return this.notify(w, false);
85
+ const rect = w.el.getBoundingClientRect();
86
+ if (rect.width === 0 || rect.height === 0) return this.notify(w, false);
87
+ const style = window.getComputedStyle(w.el);
88
+ if (style.opacity === "0" || style.visibility === "hidden") return this.notify(w, false);
89
+ const x = rect.left + rect.width / 2;
90
+ const y = rect.top + rect.height / 2;
91
+ const topEl = document.elementFromPoint(x, y);
92
+ const isVisible = topEl ? w.el.contains(topEl) || w.el === topEl : false;
93
+ this.notify(w, isVisible);
94
+ }
95
+ startPolling(w) {
96
+ if (w.state.timer) return;
97
+ const tick = () => {
98
+ if ("requestIdleCallback" in window) {
99
+ window.requestIdleCallback(() => this.checkOcclusion(w));
100
+ } else {
101
+ this.checkOcclusion(w);
102
+ }
103
+ };
104
+ tick();
105
+ w.state.timer = window.setInterval(tick, w.opts.pollInterval);
106
+ }
107
+ stopPolling(w) {
108
+ if (w.state.timer) {
109
+ clearInterval(w.state.timer);
110
+ w.state.timer = null;
111
+ }
112
+ }
113
+ notify(w, isVisible) {
114
+ if (w.state.isOccluded !== !isVisible) {
115
+ w.state.isOccluded = !isVisible;
116
+ w.cb(isVisible);
117
+ }
118
+ }
119
+ observe(el, cb, options = {}) {
120
+ if (!isBrowser()) return () => {
121
+ };
122
+ const opts = {
123
+ threshold: 0,
124
+ pollInterval: 1e3,
125
+ trackTab: true,
126
+ ...options
127
+ };
128
+ this.watchers.set(el, {
129
+ el,
130
+ cb,
131
+ opts,
132
+ state: { inViewport: false, isOccluded: false, timer: null }
133
+ });
134
+ this.observer?.observe(el);
135
+ return () => {
136
+ this.stopPolling(this.watchers.get(el));
137
+ this.observer?.unobserve(el);
138
+ this.watchers.delete(el);
139
+ };
140
+ }
141
+ };
142
+
143
+ // src/angular.ts
144
+ var realViewDirective = class {
145
+ constructor(el) {
146
+ this.el = el;
147
+ this.visibleChange = new import_core.EventEmitter();
148
+ this.stop = null;
149
+ }
150
+ ngOnInit() {
151
+ this.stop = realViewEngine.get().observe(this.el.nativeElement, (v) => {
152
+ this.visibleChange.emit(v);
153
+ }, this.options);
154
+ }
155
+ ngOnDestroy() {
156
+ this.stop?.();
157
+ }
158
+ };
159
+ __decorateClass([
160
+ (0, import_core.Input)()
161
+ ], realViewDirective.prototype, "options", 2);
162
+ __decorateClass([
163
+ (0, import_core.Output)()
164
+ ], realViewDirective.prototype, "visibleChange", 2);
165
+ realViewDirective = __decorateClass([
166
+ (0, import_core.Directive)({
167
+ selector: "[realView]",
168
+ standalone: true
169
+ })
170
+ ], realViewDirective);
171
+ // Annotate the CommonJS export names for ESM import in node:
172
+ 0 && (module.exports = {
173
+ realViewDirective
174
+ });
@@ -0,0 +1,14 @@
1
+ import { OnInit, OnDestroy, EventEmitter, ElementRef } from '@angular/core';
2
+ import { realViewOptions } from './index.cjs';
3
+
4
+ declare class realViewDirective implements OnInit, OnDestroy {
5
+ private el;
6
+ options?: realViewOptions;
7
+ visibleChange: EventEmitter<boolean>;
8
+ private stop;
9
+ constructor(el: ElementRef);
10
+ ngOnInit(): void;
11
+ ngOnDestroy(): void;
12
+ }
13
+
14
+ export { realViewDirective };
@@ -0,0 +1,14 @@
1
+ import { OnInit, OnDestroy, EventEmitter, ElementRef } from '@angular/core';
2
+ import { realViewOptions } from './index.js';
3
+
4
+ declare class realViewDirective implements OnInit, OnDestroy {
5
+ private el;
6
+ options?: realViewOptions;
7
+ visibleChange: EventEmitter<boolean>;
8
+ private stop;
9
+ constructor(el: ElementRef);
10
+ ngOnInit(): void;
11
+ ngOnDestroy(): void;
12
+ }
13
+
14
+ export { realViewDirective };
@@ -0,0 +1,37 @@
1
+ import {
2
+ __decorateClass,
3
+ realViewEngine
4
+ } from "./chunk-UW5FKFTH.js";
5
+
6
+ // src/angular.ts
7
+ import { Directive, Output, EventEmitter, Input } from "@angular/core";
8
+ var realViewDirective = class {
9
+ constructor(el) {
10
+ this.el = el;
11
+ this.visibleChange = new EventEmitter();
12
+ this.stop = null;
13
+ }
14
+ ngOnInit() {
15
+ this.stop = realViewEngine.get().observe(this.el.nativeElement, (v) => {
16
+ this.visibleChange.emit(v);
17
+ }, this.options);
18
+ }
19
+ ngOnDestroy() {
20
+ this.stop?.();
21
+ }
22
+ };
23
+ __decorateClass([
24
+ Input()
25
+ ], realViewDirective.prototype, "options", 2);
26
+ __decorateClass([
27
+ Output()
28
+ ], realViewDirective.prototype, "visibleChange", 2);
29
+ realViewDirective = __decorateClass([
30
+ Directive({
31
+ selector: "[realView]",
32
+ standalone: true
33
+ })
34
+ ], realViewDirective);
35
+ export {
36
+ realViewDirective
37
+ };
@@ -0,0 +1,123 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+
12
+ // src/core/utils.ts
13
+ var isBrowser = () => {
14
+ return typeof window !== "undefined" && typeof window.document !== "undefined";
15
+ };
16
+
17
+ // src/core/engine.ts
18
+ var realViewEngine = class _realViewEngine {
19
+ constructor() {
20
+ this.observer = null;
21
+ this.watchers = /* @__PURE__ */ new Map();
22
+ if (!isBrowser()) return;
23
+ this.initObserver();
24
+ this.initTabListener();
25
+ }
26
+ static get() {
27
+ if (!this.instance) this.instance = new _realViewEngine();
28
+ return this.instance;
29
+ }
30
+ initObserver() {
31
+ this.observer = new IntersectionObserver(this.handleIntersect.bind(this), {
32
+ threshold: 0
33
+ });
34
+ }
35
+ initTabListener() {
36
+ document.addEventListener("visibilitychange", () => {
37
+ const isHidden = document.hidden;
38
+ this.watchers.forEach((w) => {
39
+ if (w.opts.trackTab && isHidden) {
40
+ this.notify(w, false);
41
+ } else if (w.state.inViewport && !isHidden) {
42
+ this.checkOcclusion(w);
43
+ }
44
+ });
45
+ });
46
+ }
47
+ handleIntersect(entries) {
48
+ entries.forEach((entry) => {
49
+ const watcher = this.watchers.get(entry.target);
50
+ if (!watcher) return;
51
+ watcher.state.inViewport = entry.isIntersecting;
52
+ if (entry.isIntersecting) {
53
+ this.startPolling(watcher);
54
+ } else {
55
+ this.stopPolling(watcher);
56
+ this.notify(watcher, false);
57
+ }
58
+ });
59
+ }
60
+ checkOcclusion(w) {
61
+ if (w.opts.trackTab && document.hidden) return this.notify(w, false);
62
+ const rect = w.el.getBoundingClientRect();
63
+ if (rect.width === 0 || rect.height === 0) return this.notify(w, false);
64
+ const style = window.getComputedStyle(w.el);
65
+ if (style.opacity === "0" || style.visibility === "hidden") return this.notify(w, false);
66
+ const x = rect.left + rect.width / 2;
67
+ const y = rect.top + rect.height / 2;
68
+ const topEl = document.elementFromPoint(x, y);
69
+ const isVisible = topEl ? w.el.contains(topEl) || w.el === topEl : false;
70
+ this.notify(w, isVisible);
71
+ }
72
+ startPolling(w) {
73
+ if (w.state.timer) return;
74
+ const tick = () => {
75
+ if ("requestIdleCallback" in window) {
76
+ window.requestIdleCallback(() => this.checkOcclusion(w));
77
+ } else {
78
+ this.checkOcclusion(w);
79
+ }
80
+ };
81
+ tick();
82
+ w.state.timer = window.setInterval(tick, w.opts.pollInterval);
83
+ }
84
+ stopPolling(w) {
85
+ if (w.state.timer) {
86
+ clearInterval(w.state.timer);
87
+ w.state.timer = null;
88
+ }
89
+ }
90
+ notify(w, isVisible) {
91
+ if (w.state.isOccluded !== !isVisible) {
92
+ w.state.isOccluded = !isVisible;
93
+ w.cb(isVisible);
94
+ }
95
+ }
96
+ observe(el, cb, options = {}) {
97
+ if (!isBrowser()) return () => {
98
+ };
99
+ const opts = {
100
+ threshold: 0,
101
+ pollInterval: 1e3,
102
+ trackTab: true,
103
+ ...options
104
+ };
105
+ this.watchers.set(el, {
106
+ el,
107
+ cb,
108
+ opts,
109
+ state: { inViewport: false, isOccluded: false, timer: null }
110
+ });
111
+ this.observer?.observe(el);
112
+ return () => {
113
+ this.stopPolling(this.watchers.get(el));
114
+ this.observer?.unobserve(el);
115
+ this.watchers.delete(el);
116
+ };
117
+ }
118
+ };
119
+
120
+ export {
121
+ __decorateClass,
122
+ realViewEngine
123
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,136 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/index.ts
20
+ var index_exports = {};
21
+ __export(index_exports, {
22
+ realViewEngine: () => realViewEngine
23
+ });
24
+ module.exports = __toCommonJS(index_exports);
25
+
26
+ // src/core/utils.ts
27
+ var isBrowser = () => {
28
+ return typeof window !== "undefined" && typeof window.document !== "undefined";
29
+ };
30
+
31
+ // src/core/engine.ts
32
+ var realViewEngine = class _realViewEngine {
33
+ constructor() {
34
+ this.observer = null;
35
+ this.watchers = /* @__PURE__ */ new Map();
36
+ if (!isBrowser()) return;
37
+ this.initObserver();
38
+ this.initTabListener();
39
+ }
40
+ static get() {
41
+ if (!this.instance) this.instance = new _realViewEngine();
42
+ return this.instance;
43
+ }
44
+ initObserver() {
45
+ this.observer = new IntersectionObserver(this.handleIntersect.bind(this), {
46
+ threshold: 0
47
+ });
48
+ }
49
+ initTabListener() {
50
+ document.addEventListener("visibilitychange", () => {
51
+ const isHidden = document.hidden;
52
+ this.watchers.forEach((w) => {
53
+ if (w.opts.trackTab && isHidden) {
54
+ this.notify(w, false);
55
+ } else if (w.state.inViewport && !isHidden) {
56
+ this.checkOcclusion(w);
57
+ }
58
+ });
59
+ });
60
+ }
61
+ handleIntersect(entries) {
62
+ entries.forEach((entry) => {
63
+ const watcher = this.watchers.get(entry.target);
64
+ if (!watcher) return;
65
+ watcher.state.inViewport = entry.isIntersecting;
66
+ if (entry.isIntersecting) {
67
+ this.startPolling(watcher);
68
+ } else {
69
+ this.stopPolling(watcher);
70
+ this.notify(watcher, false);
71
+ }
72
+ });
73
+ }
74
+ checkOcclusion(w) {
75
+ if (w.opts.trackTab && document.hidden) return this.notify(w, false);
76
+ const rect = w.el.getBoundingClientRect();
77
+ if (rect.width === 0 || rect.height === 0) return this.notify(w, false);
78
+ const style = window.getComputedStyle(w.el);
79
+ if (style.opacity === "0" || style.visibility === "hidden") return this.notify(w, false);
80
+ const x = rect.left + rect.width / 2;
81
+ const y = rect.top + rect.height / 2;
82
+ const topEl = document.elementFromPoint(x, y);
83
+ const isVisible = topEl ? w.el.contains(topEl) || w.el === topEl : false;
84
+ this.notify(w, isVisible);
85
+ }
86
+ startPolling(w) {
87
+ if (w.state.timer) return;
88
+ const tick = () => {
89
+ if ("requestIdleCallback" in window) {
90
+ window.requestIdleCallback(() => this.checkOcclusion(w));
91
+ } else {
92
+ this.checkOcclusion(w);
93
+ }
94
+ };
95
+ tick();
96
+ w.state.timer = window.setInterval(tick, w.opts.pollInterval);
97
+ }
98
+ stopPolling(w) {
99
+ if (w.state.timer) {
100
+ clearInterval(w.state.timer);
101
+ w.state.timer = null;
102
+ }
103
+ }
104
+ notify(w, isVisible) {
105
+ if (w.state.isOccluded !== !isVisible) {
106
+ w.state.isOccluded = !isVisible;
107
+ w.cb(isVisible);
108
+ }
109
+ }
110
+ observe(el, cb, options = {}) {
111
+ if (!isBrowser()) return () => {
112
+ };
113
+ const opts = {
114
+ threshold: 0,
115
+ pollInterval: 1e3,
116
+ trackTab: true,
117
+ ...options
118
+ };
119
+ this.watchers.set(el, {
120
+ el,
121
+ cb,
122
+ opts,
123
+ state: { inViewport: false, isOccluded: false, timer: null }
124
+ });
125
+ this.observer?.observe(el);
126
+ return () => {
127
+ this.stopPolling(this.watchers.get(el));
128
+ this.observer?.unobserve(el);
129
+ this.watchers.delete(el);
130
+ };
131
+ }
132
+ };
133
+ // Annotate the CommonJS export names for ESM import in node:
134
+ 0 && (module.exports = {
135
+ realViewEngine
136
+ });