smart-adsense 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dmytro Shovchko
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,203 @@
1
+ # smart-adsense
2
+
3
+ A drop-in AdSense service configuration with a `<smart-adsense>` custom element for retry, refresh, and status tracking.
4
+
5
+ ## Features
6
+
7
+ - **Lazy loading element**: `loading="lazy"` and [Exadel Smart Library media query](https://esl-ui.com/core/esl-media-query/) `display` gate script execution until the slot is visible or matches a breakpoint.
8
+ - **Retry + refresh orchestration**: configurable delay/count for unfilled slots and interval-based refresh when filled.
9
+ - **Resize awareness**: if the `params` attribute sets `refreshOnResize=true`, a filled element re-renders whenever its width changes (attribute described in the Element API section).
10
+ - **Mutation-based status detection** via `MutationObserver`, emitting `adsense:status` (`filled`, `unfilled`, `pending`, etc.).
11
+ - **SSR/CSR safe service** built on `smart-load-manager` (mutexed script loading, early hints, debug logging support).
12
+
13
+ ## Service vs. element
14
+
15
+ - **Service (`SmartAdsense`)** - extends `SmartService` to own script loading, retries, refresh timers, and early-hint helpers. You can call it directly (`SmartAdsense.load()`, `SmartAdsense.preload()`, `SmartAdsense.setupEarlyHints()`) or wire it into `SmartLoad.queue()`.
16
+ - **Element (`<smart-adsense>`)** - wraps the service so markup can express lazy/conditional activation (`loading`, `display`), resize awareness, and DOM events for status tracking.
17
+
18
+ *Typical flow*: configure the service once (`SmartAdsense.config()`), register the element, and sprinkle `<smart-adsense>` components throughout templates. When you need bespoke orchestration, trigger the same service instance via `SmartLoad` or manual calls while the element reacts to `SmartAdsense.instance.load()` resolution.
19
+
20
+ > ⚠️ `SmartAdsense.config()` mutates global defaults for the singleton service. Call it during bootstrap (before rendering elements) and avoid per-request overrides unless you intentionally change the global state.
21
+
22
+ > ℹ️ The custom element is not auto-registered. Call `SmartAdsenseElement.register()` once during app startup (before using `<smart-adsense>` in markup).
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ npm install smart-adsense smart-load-manager @exadel/esl
28
+ ```
29
+
30
+ `smart-adsense` wraps `smart-load-manager` and re-exports pieces from it. Both packages expect [`@exadel/esl`](https://www.npmjs.com/package/@exadel/esl) as a peer dependency (you can learn more about ESL on the library [website](https://esl-ui.com/)). Your package manager will prompt if ESL is missing; if your project already ships a compatible ESL build, you can skip it from the install command above.
31
+
32
+ The CSS file `smart-adsense.css` ships in `dist/` folder - copy or import it wherever you register the element.
33
+
34
+ ## Quick start
35
+
36
+ ```ts
37
+ import {SmartAdsense, SmartAdsenseElement} from 'smart-adsense';
38
+ import 'smart-adsense/dist/smart-adsense.css';
39
+
40
+ SmartAdsense.config({
41
+ url: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXX',
42
+ retryDelay: 20,
43
+ retryCount: 3,
44
+ refreshInterval: 0,
45
+ refreshOnResize: false,
46
+ });
47
+
48
+ SmartAdsenseElement.register(); // recommended after config so early instances see your defaults (safe to swap if needed)
49
+ ```
50
+
51
+ ```html
52
+ <smart-adsense
53
+ display="(min-width: 768px)"
54
+ loading="lazy"
55
+ params='{"retryDelay":10,"refreshInterval":60}'
56
+ >
57
+ <!-- your ins data-ad-code -->
58
+ </smart-adsense>
59
+ ```
60
+
61
+ - `SmartAdsense.config()` merges defaults with global service options (delegates to `SmartService`).
62
+ - The custom element loads scripts lazily (using `SmartAdsense.instance.load()`), tracks mutations, and dispatches `smart-adsense:change` events whenever status updates.
63
+
64
+ ### Coordinating with other services
65
+
66
+ Because `SmartAdsense` extends `SmartService`, it participates in the same orchestration primitives as any other loader:
67
+
68
+ ```ts
69
+ import {SmartService, SmartLoad} from 'smart-load-manager';
70
+ import {SmartAdsense} from 'smart-adsense';
71
+
72
+ const consent = SmartService.create({name: 'Consent', url: 'https://cdn.example.com/consent.js'});
73
+ SmartAdsense.config({url: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXX'});
74
+
75
+ SmartLoad.queue(consent);
76
+ SmartLoad.queue(SmartAdsense, consent.load); // load AdSense only after the consent has been loaded
77
+ SmartLoad.start();
78
+ ```
79
+
80
+ You can also call `SmartAdsense.load()` directly and branch on success/failure to attach fallbacks.
81
+
82
+ **Idle window orchestration**
83
+
84
+ ```ts
85
+ import {SmartLoad, waitAny, waitIdle, waitUserActivity} from 'smart-load-manager';
86
+ import {SmartAdsense} from 'smart-adsense';
87
+
88
+ SmartAdsense.config({url: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXX'});
89
+
90
+ SmartLoad.queue(SmartAdsense, async () => waitAny([
91
+ // Wait until the browser provides ~50ms of idle time
92
+ waitIdle({thresholds: {duration: 50, ratio: 0.9}, timeout: 8000}),
93
+ // or until the user interacts with the page
94
+ waitUserActivity()
95
+ ]));
96
+ SmartLoad.start();
97
+ SmartAdsense.load().catch(() => console.log('?> AdSense failed! Will do something else...'));
98
+ ```
99
+
100
+ This pattern defers AdSense bootstrapping until the page settles (e.g., after above-the-fold work) or the user starts interacting with the page. Combine with other wait tasks to tailor the user journey.
101
+
102
+ ## Configuration
103
+
104
+ `SmartAdsenseOptions` extend `SmartServiceOptions`:
105
+
106
+ | Option | Type | Description |
107
+ | --- | --- | --- |
108
+ | `retryDelay` | `number` | Seconds before retrying unfilled units (0 disables retries). |
109
+ | `retryCount` | `number` | Max number of retries; `0` = unlimited. |
110
+ | `refreshInterval` | `number` | Seconds before refreshing filled ads; `0` disables refresh. |
111
+ | `refreshOnResize` | `boolean` | Refresh when element width changes after a filled state. |
112
+ | `debug` | `boolean` | When true, element logs lifecycle events. |
113
+
114
+ Use `SmartAdsense.config()` globally and override per-element via the `params` attribute (JSON string merged over defaults).
115
+
116
+ > `retryCount = 0` means "retry indefinitely". This matches the legacy AdSense behavior where omitting the limit reattempts until a fill occurs. Set it to a positive integer to cap retries, or pair with `retryDelay = 0` to disable retries entirely.
117
+
118
+ ## Element API
119
+
120
+ `<smart-adsense>` extends `SmartAdsenseElement` (an [`ESLBaseElement`](https://esl-ui.com/core/esl-base-element/)). It exposes declarative attributes, runtime properties, and status events so you can wire UI around AdSense lifecycles.
121
+
122
+ ### Static helpers
123
+
124
+ | Member | Description |
125
+ | --- | --- |
126
+ | `SmartAdsenseElement.create()` | Creates an instance of the `SmartAdsenseElement`. |
127
+ | `SmartAdsenseElement.register()` | Defines the custom element (call once after configuring `SmartAdsense`). |
128
+
129
+ ### Attributes
130
+
131
+ | Attribute | Default | Purpose |
132
+ | --- | --- | --- |
133
+ | `display` | `all` | (`ESLMediaQuery`)[https://esl-ui.com/core/esl-media-query/] expression controlling visibility/lazy init. |
134
+ | `loading` | `lazy` | `lazy` waits for intersection, `eager` loads immediately. |
135
+ | `params` | `{}` | JSON encoded partial `SmartAdsenseOptions`. |
136
+ | `status` | `init` | Reflects current lifecycle (`init`, `pending`, `filled`, `unfilled`, `hidden`, `failed`). |
137
+
138
+ ### Properties
139
+
140
+ | Property | Type | Description |
141
+ | --- | --- | --- |
142
+ | `config` | `SmartAdsenseOptions` | Read-only view of the effective configuration (global `SmartAdsense.config()` merged with `params`). Use it to inspect retry/refresh values at runtime. |
143
+ | `status` | `string` | Getter/Setter for the current lifecycle (`init`, `pending`, etc.). Setting it manually forces a lifecycle update and dispatches `smart-adsense:change`. |
144
+ | `inactive` | `boolean` | Indicates whether the slot is gated (set to `false` when the element intersects or `loading="eager"`). |
145
+ | `params` | `Partial<SmartAdsenseOptions>` | Runtime getter/setter for JSON params; changing it re-merges config on the next load. |
146
+
147
+ ### Events
148
+
149
+ - `adsense:status` - fires from the inner `<ins class="adsbygoogle">` created by Google. Listen when you need the raw AdSense signal (e.g., mirroring their exact payload or forwarding to analytics). It bubbles, so you can capture it at the document level.
150
+ - `smart-adsense:change` - emitted by the custom element whenever its `status` property updates (after debounce, including retries/refresh). Prefer this event for UI reactions, state machines, or whenever you only need the normalized status without touching Google's DOM.
151
+
152
+ ### Usage notes
153
+
154
+ - **Slot content** - Provide at least one `<ins class="adsbygoogle" ...>` child. If the slot is empty the component stays `hidden` and never requests AdSense. Multiple `<ins>` tags are technically supported (the markup is restored verbatim for retries), but Google typically expects one ad per element—use separate wrappers for distinct placements.
155
+ - **Conditional loading** - Toggle the `display` media query or switch the `loading` attribute between `lazy`/`eager`. Prefer media queries powered by [ESL's `<esl-media-query>`](https://esl-ui.com/core/esl-media-query/) so breakpoints stay declarative. The `inactive` attribute is only a status flag controlled by the component; do not modify it directly.
156
+ - **Cleanup/re-mount** - Remove the element from the DOM to destroy it. `disconnectedCallback` restores the original markup automatically, so you can reattach the element later without losing the `<ins>` payload.
157
+
158
+ ## Recipes
159
+
160
+ **Lazy + retry combo**
161
+
162
+ ```html
163
+ <smart-adsense
164
+ loading="lazy"
165
+ params='{"retryDelay":5,"retryCount":5}'
166
+ >
167
+ <ins class="adsbygoogle" data-ad-client="ca-pub-XXXX" data-ad-slot="123"></ins>
168
+ </smart-adsense>
169
+ ```
170
+
171
+ **Always refresh on resize**
172
+
173
+ ```html
174
+ <smart-adsense
175
+ params='{"refreshOnResize":true,"refreshInterval":0}'
176
+ >
177
+ <ins class="adsbygoogle" data-ad-client="ca-pub-XXXX" data-ad-slot="456"></ins>
178
+ </smart-adsense>
179
+ ```
180
+
181
+ **Track status changes**
182
+
183
+ ```ts
184
+ document.addEventListener('smart-adsense:change', (event) => {
185
+ const {detail: status} = event as CustomEvent<string>;
186
+ console.info('Ad status changed:', status);
187
+ });
188
+ ```
189
+
190
+ **Create element programmatically**
191
+
192
+ ```ts
193
+ const el = SmartAdsenseElement.create();
194
+ el.display = '(min-width: 1024px)';
195
+ el.params = {retryDelay: 15};
196
+ el.innerHTML = '<ins class="adsbygoogle" data-ad-slot="xxxxxxxxx"></ins>';
197
+ document.body.appendChild(el);
198
+ ```
199
+
200
+ ## Compatibility & performance
201
+
202
+ - **Browser support**: Requires ES2019+ syntax (async/await, optional chaining, nullish coalescing), Custom Elements, IntersectionObserver, ResizeObserver, and AbortController (same as `smart-load-manager`). Add polyfills before registering the element if you target legacy browsers (e.g., [@webcomponents/custom-elements](https://github.com/webcomponents/polyfills/tree/master/packages/custom-elements), [intersection-observer](https://github.com/w3c/IntersectionObserver/tree/main/polyfill)).
203
+ - **Performance impact**: The service defers AdSense execution via idle/interaction guards, reducing layout shifts and blocking time. Using `SmartLoad.queue()` serializes queued third-party script loads (not every resource), so only one ad/marketing script fetch runs at a time, which helps stabilize Core Web Vitals on ad-heavy pages.
package/dist/index.cjs ADDED
@@ -0,0 +1,329 @@
1
+ 'use strict';
2
+
3
+ var dom_js = require('@exadel/esl/modules/esl-utils/dom.js');
4
+ var smartLoadManager = require('smart-load-manager');
5
+ var core_js$1 = require('@exadel/esl/modules/esl-base-element/core.js');
6
+ var decorators_js = require('@exadel/esl/modules/esl-utils/decorators.js');
7
+ var async_js = require('@exadel/esl/modules/esl-utils/async.js');
8
+ var core_js = require('@exadel/esl/modules/esl-event-listener/core.js');
9
+ var intersection_target_js = require('@exadel/esl/modules/esl-event-listener/core/targets/intersection.target.js');
10
+ var core_js$2 = require('@exadel/esl/modules/esl-media-query/core.js');
11
+
12
+ const STATUS_EVENT = "adsense:status";
13
+ const AD_STATUS_ATTR = "data-ad-status";
14
+ const ADSBYGOOGLE_STATUS_ATTR = "data-adsbygoogle-status";
15
+ class SmartAdsense extends smartLoadManager.SmartService {
16
+ _initStatusObserving() {
17
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
18
+ const mutation$$ = new MutationObserver(this._onMutation);
19
+ mutation$$.observe(document.documentElement, {
20
+ subtree: true,
21
+ childList: true,
22
+ attributes: true,
23
+ attributeFilter: [AD_STATUS_ATTR, ADSBYGOOGLE_STATUS_ATTR]
24
+ });
25
+ }
26
+ _onMutation(mutations) {
27
+ mutations.forEach((record) => {
28
+ const { attributeName, target, type } = record;
29
+ if (type !== "attributes" || attributeName !== AD_STATUS_ATTR && attributeName !== ADSBYGOOGLE_STATUS_ATTR) return;
30
+ const { adStatus, adsbygoogleStatus } = target.dataset;
31
+ if (adStatus) {
32
+ dom_js.dispatchCustomEvent(target, STATUS_EVENT, { detail: adStatus });
33
+ return;
34
+ }
35
+ if (adsbygoogleStatus === "done" && !adStatus) {
36
+ const status = target.innerHTML.trim() === "" ? "unfilled" : "filled";
37
+ dom_js.dispatchCustomEvent(target, STATUS_EVENT, { detail: status });
38
+ }
39
+ });
40
+ }
41
+ _onLoadScript() {
42
+ super._onLoadScript();
43
+ this._initStatusObserving();
44
+ }
45
+ }
46
+ SmartAdsense._config = {
47
+ name: "Adsense",
48
+ retryDelay: 20,
49
+ retryCount: 3,
50
+ refreshInterval: 0,
51
+ refreshOnResize: false,
52
+ attrs: { crossorigin: "anonymous" }
53
+ };
54
+
55
+ var __defProp = Object.defineProperty;
56
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
57
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
58
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
59
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
60
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
61
+ var __spreadValues = (a, b) => {
62
+ for (var prop2 in b || (b = {}))
63
+ if (__hasOwnProp.call(b, prop2))
64
+ __defNormalProp(a, prop2, b[prop2]);
65
+ if (__getOwnPropSymbols)
66
+ for (var prop2 of __getOwnPropSymbols(b)) {
67
+ if (__propIsEnum.call(b, prop2))
68
+ __defNormalProp(a, prop2, b[prop2]);
69
+ }
70
+ return a;
71
+ };
72
+ var __decorateClass = (decorators, target, key, kind) => {
73
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
74
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
75
+ if (decorator = decorators[i])
76
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
77
+ if (kind && result) __defProp(target, key, result);
78
+ return result;
79
+ };
80
+ var __async = (__this, __arguments, generator) => {
81
+ return new Promise((resolve, reject) => {
82
+ var fulfilled = (value) => {
83
+ try {
84
+ step(generator.next(value));
85
+ } catch (e) {
86
+ reject(e);
87
+ }
88
+ };
89
+ var rejected = (value) => {
90
+ try {
91
+ step(generator.throw(value));
92
+ } catch (e) {
93
+ reject(e);
94
+ }
95
+ };
96
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
97
+ step((generator = generator.apply(__this, __arguments)).next());
98
+ });
99
+ };
100
+ class SmartAdsenseElement extends core_js$1.ESLBaseElement {
101
+ constructor() {
102
+ super(...arguments);
103
+ this.params = {};
104
+ this.inactive = true;
105
+ this._content = "";
106
+ this._lastWidth = 0;
107
+ this._retries = 0;
108
+ this._status = "init";
109
+ this._task = new async_js.DelayedTask();
110
+ }
111
+ get config() {
112
+ return __spreadValues(__spreadValues({}, SmartAdsense.config()), this.params || {});
113
+ }
114
+ get displayQuery() {
115
+ return core_js$2.ESLMediaQuery.for(this.display);
116
+ }
117
+ /** Check if ad content is empty */
118
+ get isEmpty() {
119
+ return this._content.trim() === "";
120
+ }
121
+ /** Current ad status */
122
+ get status() {
123
+ return this._status;
124
+ }
125
+ set status(value) {
126
+ if (this._status === value) return;
127
+ this.setAttribute("status", this._status = value);
128
+ this._onStatusChange();
129
+ }
130
+ /** IntersectionObserver rootMargin value */
131
+ get rootMargin() {
132
+ return `${this.baseMargin * this.connectionRatio}px`;
133
+ }
134
+ /** Connection speed ratio */
135
+ get connectionRatio() {
136
+ var _a;
137
+ switch ((_a = navigator.connection) == null ? void 0 : _a.effectiveType) {
138
+ case "slow-2g":
139
+ case "2g":
140
+ return 2;
141
+ case "3g":
142
+ return 1.5;
143
+ case "4g":
144
+ default:
145
+ return 1;
146
+ }
147
+ }
148
+ connectedCallback() {
149
+ super.connectedCallback();
150
+ this.inactive = this.loading !== "eager";
151
+ this.storeContent();
152
+ this.init();
153
+ this.initA11y();
154
+ }
155
+ disconnectedCallback() {
156
+ super.disconnectedCallback();
157
+ this.restoreContent();
158
+ }
159
+ attributeChangedCallback(attrName, oldVal, newVal) {
160
+ if (!this.connected) return;
161
+ if (attrName === "display") {
162
+ decorators_js.memoize.clear(this, "displayQuery");
163
+ this.$$on(this._onDisplayChange);
164
+ this.init();
165
+ }
166
+ }
167
+ /** Initializes ad */
168
+ init() {
169
+ return __async(this, null, function* () {
170
+ if (this.isEmpty) return;
171
+ if (!this.displayQuery.matches) {
172
+ this._task.put(this.hideAd);
173
+ return;
174
+ }
175
+ if (this.inactive) return;
176
+ try {
177
+ yield SmartAdsense.instance.load();
178
+ this._task.put(this.refreshAd, 0);
179
+ } catch (e) {
180
+ this.status = "failed";
181
+ }
182
+ });
183
+ }
184
+ /** Sets initial a11y attributes */
185
+ initA11y() {
186
+ if (!this.hasAttribute("role")) this.setAttribute("role", "complementary");
187
+ }
188
+ /** Stores initial ad content */
189
+ storeContent() {
190
+ this._content = this.innerHTML;
191
+ this.innerHTML = "";
192
+ }
193
+ /** Restores initial ad content */
194
+ restoreContent() {
195
+ this.innerHTML = this._content;
196
+ }
197
+ /** Pushes ad to the DOM */
198
+ pushAd() {
199
+ try {
200
+ (adsbygoogle = window.adsbygoogle || []).push({});
201
+ } catch (e) {
202
+ }
203
+ }
204
+ hideAd() {
205
+ this.status = "hidden";
206
+ this.innerHTML = "";
207
+ this._retries = 0;
208
+ }
209
+ refreshAd() {
210
+ this.status = "pending";
211
+ this._retries++;
212
+ this.restoreContent();
213
+ this.pushAd();
214
+ }
215
+ _onResize(event) {
216
+ const { contentRect } = event;
217
+ if (this._lastWidth === contentRect.width) return;
218
+ if (this._lastWidth > 0 && this.status === "filled") {
219
+ this._retries = 0;
220
+ this._task.put(this.refreshAd, 0);
221
+ this._log("Ad is refreshing on resize", this._lastWidth);
222
+ }
223
+ this._lastWidth = contentRect.width;
224
+ }
225
+ _onAdsenseStatusChange(event) {
226
+ this._log("Ad status:", event.detail);
227
+ this.status = event.detail;
228
+ if (event.detail === "unfilled") {
229
+ const { retryCount, retryDelay } = this.config;
230
+ const canRetry = retryCount === 0 || this._retries < retryCount;
231
+ if (retryDelay > 0 && canRetry) {
232
+ this._task.put(this.refreshAd, retryDelay * 1e3);
233
+ this._log(`Ad unfilled will be reattempted in ${retryDelay}s (${this._retries})`);
234
+ }
235
+ } else {
236
+ const { refreshInterval } = this.config;
237
+ this._log("Refresh interval:", refreshInterval);
238
+ if (refreshInterval > 0) {
239
+ this._task.put(this.refreshAd, (refreshInterval + Math.random()) * 1e3);
240
+ this._log(`Ad filled will be refreshed in ${refreshInterval}s [${this._retries}]`);
241
+ }
242
+ }
243
+ }
244
+ _onIntersect(e) {
245
+ this.inactive = false;
246
+ this.init();
247
+ }
248
+ _onDisplayChange(event) {
249
+ this.init();
250
+ }
251
+ _onStatusChange() {
252
+ dom_js.dispatchCustomEvent(this, "smart-adsense:change", { bubbles: false });
253
+ }
254
+ /** Logs messages to the console if debug mode is enabled */
255
+ _log(...args) {
256
+ if (!this.config.debug) return;
257
+ console.log(...args);
258
+ }
259
+ }
260
+ SmartAdsenseElement.is = "smart-adsense";
261
+ SmartAdsenseElement.observedAttributes = ["display"];
262
+ __decorateClass([
263
+ decorators_js.prop(750)
264
+ ], SmartAdsenseElement.prototype, "baseMargin", 2);
265
+ __decorateClass([
266
+ decorators_js.prop([0, 0.01])
267
+ ], SmartAdsenseElement.prototype, "INTERSECTION_THRESHOLD", 2);
268
+ __decorateClass([
269
+ decorators_js.prop("smart-adsense:change")
270
+ ], SmartAdsenseElement.prototype, "CHANGE_EVENT", 2);
271
+ __decorateClass([
272
+ decorators_js.attr({ defaultValue: "all" })
273
+ ], SmartAdsenseElement.prototype, "display", 2);
274
+ __decorateClass([
275
+ decorators_js.attr({ defaultValue: "lazy" })
276
+ ], SmartAdsenseElement.prototype, "loading", 2);
277
+ __decorateClass([
278
+ decorators_js.jsonAttr()
279
+ ], SmartAdsenseElement.prototype, "params", 2);
280
+ __decorateClass([
281
+ decorators_js.boolAttr()
282
+ ], SmartAdsenseElement.prototype, "inactive", 2);
283
+ __decorateClass([
284
+ decorators_js.memoize()
285
+ ], SmartAdsenseElement.prototype, "config", 1);
286
+ __decorateClass([
287
+ decorators_js.memoize()
288
+ ], SmartAdsenseElement.prototype, "displayQuery", 1);
289
+ __decorateClass([
290
+ decorators_js.bind
291
+ ], SmartAdsenseElement.prototype, "hideAd", 1);
292
+ __decorateClass([
293
+ decorators_js.bind
294
+ ], SmartAdsenseElement.prototype, "refreshAd", 1);
295
+ __decorateClass([
296
+ decorators_js.listen({
297
+ event: "resize",
298
+ condition: ($this) => {
299
+ var _a;
300
+ return (_a = $this.config.refreshOnResize) != null ? _a : false;
301
+ },
302
+ target: core_js.ESLResizeObserverTarget.for
303
+ }),
304
+ decorators_js.decorate(async_js.debounce, 1e3)
305
+ ], SmartAdsenseElement.prototype, "_onResize", 1);
306
+ __decorateClass([
307
+ decorators_js.listen("adsense:status")
308
+ ], SmartAdsenseElement.prototype, "_onAdsenseStatusChange", 1);
309
+ __decorateClass([
310
+ decorators_js.listen({
311
+ event: intersection_target_js.ESLIntersectionEvent.IN,
312
+ once: true,
313
+ condition: (that) => that.loading !== "eager",
314
+ target: (that) => intersection_target_js.ESLIntersectionTarget.for(that, {
315
+ rootMargin: that.rootMargin,
316
+ threshold: that.INTERSECTION_THRESHOLD
317
+ })
318
+ })
319
+ ], SmartAdsenseElement.prototype, "_onIntersect", 1);
320
+ __decorateClass([
321
+ decorators_js.listen({ event: "change", target: ($this) => $this.displayQuery })
322
+ ], SmartAdsenseElement.prototype, "_onDisplayChange", 1);
323
+ __decorateClass([
324
+ decorators_js.decorate(async_js.microtask)
325
+ ], SmartAdsenseElement.prototype, "_onStatusChange", 1);
326
+
327
+ exports.STATUS_EVENT = STATUS_EVENT;
328
+ exports.SmartAdsense = SmartAdsense;
329
+ exports.SmartAdsenseElement = SmartAdsenseElement;
@@ -0,0 +1,113 @@
1
+ import { SmartServiceOptions, SmartService } from 'smart-load-manager';
2
+ import { ESLBaseElement } from '@exadel/esl/modules/esl-base-element/core';
3
+ import { DelayedTask } from '@exadel/esl/modules/esl-utils/async';
4
+ import { ESLIntersectionEvent } from '@exadel/esl/modules/esl-event-listener/core/targets/intersection.target';
5
+ import { ESLMediaQuery, ESLMediaChangeEvent } from '@exadel/esl/modules/esl-media-query/core';
6
+ import { ESLElementResizeEvent } from '@exadel/esl/modules/esl-event-listener/core';
7
+
8
+ /** Configuration options for SmartAdsense */
9
+ interface SmartAdsenseOptions extends SmartServiceOptions {
10
+ /** Retrying unfilled ads: delay between retry attempts in seconds, 0 means no retrying */
11
+ retryDelay: number;
12
+ /** Retrying unfilled ads: number of retry attempts, 0 means infinite */
13
+ retryCount: number;
14
+ /** Refreshing filled ads: interval in seconds, 0 means no refresh */
15
+ refreshInterval: number;
16
+ /** Refreshing filled ads: refresh on ad block resize, false means no refresh */
17
+ refreshOnResize: boolean;
18
+ }
19
+ declare const STATUS_EVENT = "adsense:status";
20
+ /** SmartAdsense service class for managing adsense scripts and status observing */
21
+ declare class SmartAdsense extends SmartService {
22
+ protected static _config: SmartAdsenseOptions;
23
+ protected _initStatusObserving(): void;
24
+ protected _onMutation(mutations: MutationRecord[]): void;
25
+ protected _onLoadScript(): void;
26
+ }
27
+
28
+ /** SmartAdsense web component for displaying Adsense ads */
29
+ declare class SmartAdsenseElement extends ESLBaseElement {
30
+ static is: string;
31
+ static observedAttributes: string[];
32
+ baseMargin: number;
33
+ protected INTERSECTION_THRESHOLD: number[];
34
+ CHANGE_EVENT: string;
35
+ /** Condition {@link ESLMediaQuery} to allow display of ad. Default: `all` */
36
+ display: string;
37
+ /** Ad loading strategy ('lazy' or 'eager'). Default: `lazy` */
38
+ loading: string;
39
+ /** Configuration parameters for SmartAdsense */
40
+ params: Partial<SmartAdsenseOptions>;
41
+ /** Inactive state of the ad (not loaded until becomes active) */
42
+ inactive: boolean;
43
+ protected _content: string;
44
+ protected _lastWidth: number;
45
+ protected _retries: number;
46
+ protected _status: string;
47
+ protected _task: DelayedTask;
48
+ /** Current configuration */
49
+ get config(): SmartAdsenseOptions;
50
+ /** ESLMediaQuery to limit display */
51
+ protected get displayQuery(): ESLMediaQuery;
52
+ /** Check if ad content is empty */
53
+ protected get isEmpty(): boolean;
54
+ /** Current ad status */
55
+ get status(): string;
56
+ set status(value: string);
57
+ /** IntersectionObserver rootMargin value */
58
+ protected get rootMargin(): string;
59
+ /** Connection speed ratio */
60
+ protected get connectionRatio(): number;
61
+ protected connectedCallback(): void;
62
+ protected disconnectedCallback(): void;
63
+ protected attributeChangedCallback(attrName: string, oldVal: string, newVal: string): void;
64
+ /** Initializes ad */
65
+ protected init(): Promise<void>;
66
+ /** Sets initial a11y attributes */
67
+ protected initA11y(): void;
68
+ /** Stores initial ad content */
69
+ protected storeContent(): void;
70
+ /** Restores initial ad content */
71
+ protected restoreContent(): void;
72
+ /** Pushes ad to the DOM */
73
+ protected pushAd(): void;
74
+ /** Hides the ad */
75
+ protected hideAd(): void;
76
+ /** Refreshes the ad */
77
+ protected refreshAd(): void;
78
+ /** Handles resize of ad */
79
+ protected _onResize(event: ESLElementResizeEvent): void;
80
+ /** Handles adsense status change */
81
+ protected _onAdsenseStatusChange(event: CustomEvent): void;
82
+ /** Handles intersection event to initialize ad loading */
83
+ protected _onIntersect(e: ESLIntersectionEvent): void;
84
+ /** Handles display query change and processes reinitialization of ad */
85
+ protected _onDisplayChange(event?: ESLMediaChangeEvent): void;
86
+ /** Handles status changing */
87
+ protected _onStatusChange(): void;
88
+ /** Logs messages to the console if debug mode is enabled */
89
+ protected _log(...args: unknown[]): void;
90
+ }
91
+ declare global {
92
+ export interface HTMLElementTagNameMap {
93
+ 'smart-adsense': SmartAdsenseElement;
94
+ }
95
+ let adsbygoogle: {
96
+ [key: string]: unknown;
97
+ }[];
98
+ interface Window {
99
+ adsbygoogle: {
100
+ [key: string]: unknown;
101
+ }[];
102
+ }
103
+ interface Navigator extends NavigatorNetworkInformation {
104
+ }
105
+ interface NavigatorNetworkInformation {
106
+ readonly connection?: {
107
+ readonly effectiveType: 'slow-2g' | '2g' | '3g' | '4g';
108
+ };
109
+ }
110
+ }
111
+
112
+ export { STATUS_EVENT, SmartAdsense, SmartAdsenseElement };
113
+ export type { SmartAdsenseOptions };
@@ -0,0 +1,113 @@
1
+ import { SmartServiceOptions, SmartService } from 'smart-load-manager';
2
+ import { ESLBaseElement } from '@exadel/esl/modules/esl-base-element/core';
3
+ import { DelayedTask } from '@exadel/esl/modules/esl-utils/async';
4
+ import { ESLIntersectionEvent } from '@exadel/esl/modules/esl-event-listener/core/targets/intersection.target';
5
+ import { ESLMediaQuery, ESLMediaChangeEvent } from '@exadel/esl/modules/esl-media-query/core';
6
+ import { ESLElementResizeEvent } from '@exadel/esl/modules/esl-event-listener/core';
7
+
8
+ /** Configuration options for SmartAdsense */
9
+ interface SmartAdsenseOptions extends SmartServiceOptions {
10
+ /** Retrying unfilled ads: delay between retry attempts in seconds, 0 means no retrying */
11
+ retryDelay: number;
12
+ /** Retrying unfilled ads: number of retry attempts, 0 means infinite */
13
+ retryCount: number;
14
+ /** Refreshing filled ads: interval in seconds, 0 means no refresh */
15
+ refreshInterval: number;
16
+ /** Refreshing filled ads: refresh on ad block resize, false means no refresh */
17
+ refreshOnResize: boolean;
18
+ }
19
+ declare const STATUS_EVENT = "adsense:status";
20
+ /** SmartAdsense service class for managing adsense scripts and status observing */
21
+ declare class SmartAdsense extends SmartService {
22
+ protected static _config: SmartAdsenseOptions;
23
+ protected _initStatusObserving(): void;
24
+ protected _onMutation(mutations: MutationRecord[]): void;
25
+ protected _onLoadScript(): void;
26
+ }
27
+
28
+ /** SmartAdsense web component for displaying Adsense ads */
29
+ declare class SmartAdsenseElement extends ESLBaseElement {
30
+ static is: string;
31
+ static observedAttributes: string[];
32
+ baseMargin: number;
33
+ protected INTERSECTION_THRESHOLD: number[];
34
+ CHANGE_EVENT: string;
35
+ /** Condition {@link ESLMediaQuery} to allow display of ad. Default: `all` */
36
+ display: string;
37
+ /** Ad loading strategy ('lazy' or 'eager'). Default: `lazy` */
38
+ loading: string;
39
+ /** Configuration parameters for SmartAdsense */
40
+ params: Partial<SmartAdsenseOptions>;
41
+ /** Inactive state of the ad (not loaded until becomes active) */
42
+ inactive: boolean;
43
+ protected _content: string;
44
+ protected _lastWidth: number;
45
+ protected _retries: number;
46
+ protected _status: string;
47
+ protected _task: DelayedTask;
48
+ /** Current configuration */
49
+ get config(): SmartAdsenseOptions;
50
+ /** ESLMediaQuery to limit display */
51
+ protected get displayQuery(): ESLMediaQuery;
52
+ /** Check if ad content is empty */
53
+ protected get isEmpty(): boolean;
54
+ /** Current ad status */
55
+ get status(): string;
56
+ set status(value: string);
57
+ /** IntersectionObserver rootMargin value */
58
+ protected get rootMargin(): string;
59
+ /** Connection speed ratio */
60
+ protected get connectionRatio(): number;
61
+ protected connectedCallback(): void;
62
+ protected disconnectedCallback(): void;
63
+ protected attributeChangedCallback(attrName: string, oldVal: string, newVal: string): void;
64
+ /** Initializes ad */
65
+ protected init(): Promise<void>;
66
+ /** Sets initial a11y attributes */
67
+ protected initA11y(): void;
68
+ /** Stores initial ad content */
69
+ protected storeContent(): void;
70
+ /** Restores initial ad content */
71
+ protected restoreContent(): void;
72
+ /** Pushes ad to the DOM */
73
+ protected pushAd(): void;
74
+ /** Hides the ad */
75
+ protected hideAd(): void;
76
+ /** Refreshes the ad */
77
+ protected refreshAd(): void;
78
+ /** Handles resize of ad */
79
+ protected _onResize(event: ESLElementResizeEvent): void;
80
+ /** Handles adsense status change */
81
+ protected _onAdsenseStatusChange(event: CustomEvent): void;
82
+ /** Handles intersection event to initialize ad loading */
83
+ protected _onIntersect(e: ESLIntersectionEvent): void;
84
+ /** Handles display query change and processes reinitialization of ad */
85
+ protected _onDisplayChange(event?: ESLMediaChangeEvent): void;
86
+ /** Handles status changing */
87
+ protected _onStatusChange(): void;
88
+ /** Logs messages to the console if debug mode is enabled */
89
+ protected _log(...args: unknown[]): void;
90
+ }
91
+ declare global {
92
+ export interface HTMLElementTagNameMap {
93
+ 'smart-adsense': SmartAdsenseElement;
94
+ }
95
+ let adsbygoogle: {
96
+ [key: string]: unknown;
97
+ }[];
98
+ interface Window {
99
+ adsbygoogle: {
100
+ [key: string]: unknown;
101
+ }[];
102
+ }
103
+ interface Navigator extends NavigatorNetworkInformation {
104
+ }
105
+ interface NavigatorNetworkInformation {
106
+ readonly connection?: {
107
+ readonly effectiveType: 'slow-2g' | '2g' | '3g' | '4g';
108
+ };
109
+ }
110
+ }
111
+
112
+ export { STATUS_EVENT, SmartAdsense, SmartAdsenseElement };
113
+ export type { SmartAdsenseOptions };
package/dist/index.mjs ADDED
@@ -0,0 +1,325 @@
1
+ import { dispatchCustomEvent } from '@exadel/esl/modules/esl-utils/dom.js';
2
+ import { SmartService } from 'smart-load-manager';
3
+ import { ESLBaseElement } from '@exadel/esl/modules/esl-base-element/core.js';
4
+ import { prop, attr, jsonAttr, boolAttr, memoize, bind, listen, decorate } from '@exadel/esl/modules/esl-utils/decorators.js';
5
+ import { debounce, microtask, DelayedTask } from '@exadel/esl/modules/esl-utils/async.js';
6
+ import { ESLResizeObserverTarget } from '@exadel/esl/modules/esl-event-listener/core.js';
7
+ import { ESLIntersectionTarget, ESLIntersectionEvent } from '@exadel/esl/modules/esl-event-listener/core/targets/intersection.target.js';
8
+ import { ESLMediaQuery } from '@exadel/esl/modules/esl-media-query/core.js';
9
+
10
+ const STATUS_EVENT = "adsense:status";
11
+ const AD_STATUS_ATTR = "data-ad-status";
12
+ const ADSBYGOOGLE_STATUS_ATTR = "data-adsbygoogle-status";
13
+ class SmartAdsense extends SmartService {
14
+ _initStatusObserving() {
15
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
16
+ const mutation$$ = new MutationObserver(this._onMutation);
17
+ mutation$$.observe(document.documentElement, {
18
+ subtree: true,
19
+ childList: true,
20
+ attributes: true,
21
+ attributeFilter: [AD_STATUS_ATTR, ADSBYGOOGLE_STATUS_ATTR]
22
+ });
23
+ }
24
+ _onMutation(mutations) {
25
+ mutations.forEach((record) => {
26
+ const { attributeName, target, type } = record;
27
+ if (type !== "attributes" || attributeName !== AD_STATUS_ATTR && attributeName !== ADSBYGOOGLE_STATUS_ATTR) return;
28
+ const { adStatus, adsbygoogleStatus } = target.dataset;
29
+ if (adStatus) {
30
+ dispatchCustomEvent(target, STATUS_EVENT, { detail: adStatus });
31
+ return;
32
+ }
33
+ if (adsbygoogleStatus === "done" && !adStatus) {
34
+ const status = target.innerHTML.trim() === "" ? "unfilled" : "filled";
35
+ dispatchCustomEvent(target, STATUS_EVENT, { detail: status });
36
+ }
37
+ });
38
+ }
39
+ _onLoadScript() {
40
+ super._onLoadScript();
41
+ this._initStatusObserving();
42
+ }
43
+ }
44
+ SmartAdsense._config = {
45
+ name: "Adsense",
46
+ retryDelay: 20,
47
+ retryCount: 3,
48
+ refreshInterval: 0,
49
+ refreshOnResize: false,
50
+ attrs: { crossorigin: "anonymous" }
51
+ };
52
+
53
+ var __defProp = Object.defineProperty;
54
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
55
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
56
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
57
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
58
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
59
+ var __spreadValues = (a, b) => {
60
+ for (var prop2 in b || (b = {}))
61
+ if (__hasOwnProp.call(b, prop2))
62
+ __defNormalProp(a, prop2, b[prop2]);
63
+ if (__getOwnPropSymbols)
64
+ for (var prop2 of __getOwnPropSymbols(b)) {
65
+ if (__propIsEnum.call(b, prop2))
66
+ __defNormalProp(a, prop2, b[prop2]);
67
+ }
68
+ return a;
69
+ };
70
+ var __decorateClass = (decorators, target, key, kind) => {
71
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
72
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
73
+ if (decorator = decorators[i])
74
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
75
+ if (kind && result) __defProp(target, key, result);
76
+ return result;
77
+ };
78
+ var __async = (__this, __arguments, generator) => {
79
+ return new Promise((resolve, reject) => {
80
+ var fulfilled = (value) => {
81
+ try {
82
+ step(generator.next(value));
83
+ } catch (e) {
84
+ reject(e);
85
+ }
86
+ };
87
+ var rejected = (value) => {
88
+ try {
89
+ step(generator.throw(value));
90
+ } catch (e) {
91
+ reject(e);
92
+ }
93
+ };
94
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
95
+ step((generator = generator.apply(__this, __arguments)).next());
96
+ });
97
+ };
98
+ class SmartAdsenseElement extends ESLBaseElement {
99
+ constructor() {
100
+ super(...arguments);
101
+ this.params = {};
102
+ this.inactive = true;
103
+ this._content = "";
104
+ this._lastWidth = 0;
105
+ this._retries = 0;
106
+ this._status = "init";
107
+ this._task = new DelayedTask();
108
+ }
109
+ get config() {
110
+ return __spreadValues(__spreadValues({}, SmartAdsense.config()), this.params || {});
111
+ }
112
+ get displayQuery() {
113
+ return ESLMediaQuery.for(this.display);
114
+ }
115
+ /** Check if ad content is empty */
116
+ get isEmpty() {
117
+ return this._content.trim() === "";
118
+ }
119
+ /** Current ad status */
120
+ get status() {
121
+ return this._status;
122
+ }
123
+ set status(value) {
124
+ if (this._status === value) return;
125
+ this.setAttribute("status", this._status = value);
126
+ this._onStatusChange();
127
+ }
128
+ /** IntersectionObserver rootMargin value */
129
+ get rootMargin() {
130
+ return `${this.baseMargin * this.connectionRatio}px`;
131
+ }
132
+ /** Connection speed ratio */
133
+ get connectionRatio() {
134
+ var _a;
135
+ switch ((_a = navigator.connection) == null ? void 0 : _a.effectiveType) {
136
+ case "slow-2g":
137
+ case "2g":
138
+ return 2;
139
+ case "3g":
140
+ return 1.5;
141
+ case "4g":
142
+ default:
143
+ return 1;
144
+ }
145
+ }
146
+ connectedCallback() {
147
+ super.connectedCallback();
148
+ this.inactive = this.loading !== "eager";
149
+ this.storeContent();
150
+ this.init();
151
+ this.initA11y();
152
+ }
153
+ disconnectedCallback() {
154
+ super.disconnectedCallback();
155
+ this.restoreContent();
156
+ }
157
+ attributeChangedCallback(attrName, oldVal, newVal) {
158
+ if (!this.connected) return;
159
+ if (attrName === "display") {
160
+ memoize.clear(this, "displayQuery");
161
+ this.$$on(this._onDisplayChange);
162
+ this.init();
163
+ }
164
+ }
165
+ /** Initializes ad */
166
+ init() {
167
+ return __async(this, null, function* () {
168
+ if (this.isEmpty) return;
169
+ if (!this.displayQuery.matches) {
170
+ this._task.put(this.hideAd);
171
+ return;
172
+ }
173
+ if (this.inactive) return;
174
+ try {
175
+ yield SmartAdsense.instance.load();
176
+ this._task.put(this.refreshAd, 0);
177
+ } catch (e) {
178
+ this.status = "failed";
179
+ }
180
+ });
181
+ }
182
+ /** Sets initial a11y attributes */
183
+ initA11y() {
184
+ if (!this.hasAttribute("role")) this.setAttribute("role", "complementary");
185
+ }
186
+ /** Stores initial ad content */
187
+ storeContent() {
188
+ this._content = this.innerHTML;
189
+ this.innerHTML = "";
190
+ }
191
+ /** Restores initial ad content */
192
+ restoreContent() {
193
+ this.innerHTML = this._content;
194
+ }
195
+ /** Pushes ad to the DOM */
196
+ pushAd() {
197
+ try {
198
+ (adsbygoogle = window.adsbygoogle || []).push({});
199
+ } catch (e) {
200
+ }
201
+ }
202
+ hideAd() {
203
+ this.status = "hidden";
204
+ this.innerHTML = "";
205
+ this._retries = 0;
206
+ }
207
+ refreshAd() {
208
+ this.status = "pending";
209
+ this._retries++;
210
+ this.restoreContent();
211
+ this.pushAd();
212
+ }
213
+ _onResize(event) {
214
+ const { contentRect } = event;
215
+ if (this._lastWidth === contentRect.width) return;
216
+ if (this._lastWidth > 0 && this.status === "filled") {
217
+ this._retries = 0;
218
+ this._task.put(this.refreshAd, 0);
219
+ this._log("Ad is refreshing on resize", this._lastWidth);
220
+ }
221
+ this._lastWidth = contentRect.width;
222
+ }
223
+ _onAdsenseStatusChange(event) {
224
+ this._log("Ad status:", event.detail);
225
+ this.status = event.detail;
226
+ if (event.detail === "unfilled") {
227
+ const { retryCount, retryDelay } = this.config;
228
+ const canRetry = retryCount === 0 || this._retries < retryCount;
229
+ if (retryDelay > 0 && canRetry) {
230
+ this._task.put(this.refreshAd, retryDelay * 1e3);
231
+ this._log(`Ad unfilled will be reattempted in ${retryDelay}s (${this._retries})`);
232
+ }
233
+ } else {
234
+ const { refreshInterval } = this.config;
235
+ this._log("Refresh interval:", refreshInterval);
236
+ if (refreshInterval > 0) {
237
+ this._task.put(this.refreshAd, (refreshInterval + Math.random()) * 1e3);
238
+ this._log(`Ad filled will be refreshed in ${refreshInterval}s [${this._retries}]`);
239
+ }
240
+ }
241
+ }
242
+ _onIntersect(e) {
243
+ this.inactive = false;
244
+ this.init();
245
+ }
246
+ _onDisplayChange(event) {
247
+ this.init();
248
+ }
249
+ _onStatusChange() {
250
+ dispatchCustomEvent(this, "smart-adsense:change", { bubbles: false });
251
+ }
252
+ /** Logs messages to the console if debug mode is enabled */
253
+ _log(...args) {
254
+ if (!this.config.debug) return;
255
+ console.log(...args);
256
+ }
257
+ }
258
+ SmartAdsenseElement.is = "smart-adsense";
259
+ SmartAdsenseElement.observedAttributes = ["display"];
260
+ __decorateClass([
261
+ prop(750)
262
+ ], SmartAdsenseElement.prototype, "baseMargin", 2);
263
+ __decorateClass([
264
+ prop([0, 0.01])
265
+ ], SmartAdsenseElement.prototype, "INTERSECTION_THRESHOLD", 2);
266
+ __decorateClass([
267
+ prop("smart-adsense:change")
268
+ ], SmartAdsenseElement.prototype, "CHANGE_EVENT", 2);
269
+ __decorateClass([
270
+ attr({ defaultValue: "all" })
271
+ ], SmartAdsenseElement.prototype, "display", 2);
272
+ __decorateClass([
273
+ attr({ defaultValue: "lazy" })
274
+ ], SmartAdsenseElement.prototype, "loading", 2);
275
+ __decorateClass([
276
+ jsonAttr()
277
+ ], SmartAdsenseElement.prototype, "params", 2);
278
+ __decorateClass([
279
+ boolAttr()
280
+ ], SmartAdsenseElement.prototype, "inactive", 2);
281
+ __decorateClass([
282
+ memoize()
283
+ ], SmartAdsenseElement.prototype, "config", 1);
284
+ __decorateClass([
285
+ memoize()
286
+ ], SmartAdsenseElement.prototype, "displayQuery", 1);
287
+ __decorateClass([
288
+ bind
289
+ ], SmartAdsenseElement.prototype, "hideAd", 1);
290
+ __decorateClass([
291
+ bind
292
+ ], SmartAdsenseElement.prototype, "refreshAd", 1);
293
+ __decorateClass([
294
+ listen({
295
+ event: "resize",
296
+ condition: ($this) => {
297
+ var _a;
298
+ return (_a = $this.config.refreshOnResize) != null ? _a : false;
299
+ },
300
+ target: ESLResizeObserverTarget.for
301
+ }),
302
+ decorate(debounce, 1e3)
303
+ ], SmartAdsenseElement.prototype, "_onResize", 1);
304
+ __decorateClass([
305
+ listen("adsense:status")
306
+ ], SmartAdsenseElement.prototype, "_onAdsenseStatusChange", 1);
307
+ __decorateClass([
308
+ listen({
309
+ event: ESLIntersectionEvent.IN,
310
+ once: true,
311
+ condition: (that) => that.loading !== "eager",
312
+ target: (that) => ESLIntersectionTarget.for(that, {
313
+ rootMargin: that.rootMargin,
314
+ threshold: that.INTERSECTION_THRESHOLD
315
+ })
316
+ })
317
+ ], SmartAdsenseElement.prototype, "_onIntersect", 1);
318
+ __decorateClass([
319
+ listen({ event: "change", target: ($this) => $this.displayQuery })
320
+ ], SmartAdsenseElement.prototype, "_onDisplayChange", 1);
321
+ __decorateClass([
322
+ decorate(microtask)
323
+ ], SmartAdsenseElement.prototype, "_onStatusChange", 1);
324
+
325
+ export { STATUS_EVENT, SmartAdsense, SmartAdsenseElement };
@@ -0,0 +1,26 @@
1
+ smart-adsense {
2
+ display: block;
3
+ overflow: hidden;
4
+ }
5
+
6
+ smart-adsense[status=hidden] {
7
+ display: none;
8
+ }
9
+
10
+ .smart-adsense-container {
11
+ display: flex;
12
+ align-items: center;
13
+ }
14
+
15
+ .smart-adsense-container smart-adsense {
16
+ height: 100%;
17
+ width: 100%;
18
+ }
19
+
20
+ .smart-adsense-container smart-adsense[status=filled] {
21
+ height: auto;
22
+ }
23
+
24
+ .smart-adsense-container:has(smart-adsense[status=hidden]) {
25
+ display: none;
26
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "smart-adsense",
3
+ "version": "1.0.0",
4
+ "description": "A service and web component for enhanced loading and management of AdSense ad blocks in your web application",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.cts",
8
+ "exports": {
9
+ "require": {
10
+ "types": "./dist/index.d.cts",
11
+ "default": "./dist/index.cjs"
12
+ },
13
+ "import": {
14
+ "types": "./dist/index.d.mts",
15
+ "default": "./dist/index.mjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/dshovchko/smart-services.git",
24
+ "directory": "packages/smart-adsense"
25
+ },
26
+ "keywords": [
27
+ "ads",
28
+ "adsense",
29
+ "lazy-load",
30
+ "web-component",
31
+ "performance",
32
+ "optimization",
33
+ "core-web-vitals"
34
+ ],
35
+ "author": "Dmytro Shovchko",
36
+ "license": "MIT",
37
+ "dependencies": {
38
+ "smart-load-manager": "^1.0.0"
39
+ },
40
+ "scripts": {
41
+ "clear": "rm -rf dist",
42
+ "build": "npm run clear && pkgroll && cp src/smart-adsense.css dist/",
43
+ "lint": "eslint \"src/**/*.ts\" --max-warnings 3",
44
+ "test": "npm run type-check && npm run lint && npm run test:run",
45
+ "test:run": "vitest run",
46
+ "test:watch": "vitest watch",
47
+ "type-check": "tsc --noEmit -p tsconfig.type-check.json"
48
+ },
49
+ "peerDependencies": {
50
+ "@exadel/esl": "^5.15.0"
51
+ }
52
+ }