universal-ad-loader 0.1.1 → 0.2.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/CHANGELOG.md +24 -0
- package/README.md +58 -4
- package/dist/index.cjs +363 -46
- package/dist/index.d.ts +46 -4
- package/dist/index.mjs +363 -46
- package/dist/ual.js +363 -46
- package/dist/ual.min.js +24 -5
- package/docs/cdn.md +30 -15
- package/docs/esempio.md +1 -1
- package/package.json +3 -3
- package/schema/ual-config.schema.json +9 -1
package/dist/ual.js
CHANGED
|
@@ -272,6 +272,247 @@
|
|
|
272
272
|
return out;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
// src/core/native.js
|
|
276
|
+
var NATIVE_LIMITS = { title: 150, body: 300, sponsoredBy: 60, cta: 30, displayUrl: 100, price: 30, label: 20 };
|
|
277
|
+
var NATIVE_LAYOUTS = ["card", "list", "compact"];
|
|
278
|
+
var clip = (v, n) => typeof v === "string" ? v.trim().slice(0, n) : "";
|
|
279
|
+
function normalizeNative(raw, safeUrl2) {
|
|
280
|
+
if (!raw || typeof raw !== "object") return { ok: false, reason: "native mancante" };
|
|
281
|
+
const title = clip(raw.title, NATIVE_LIMITS.title);
|
|
282
|
+
if (!title) return { ok: false, reason: "native: titolo mancante" };
|
|
283
|
+
const img = (v) => {
|
|
284
|
+
const o = typeof v === "string" ? { src: v } : v;
|
|
285
|
+
const src = o && typeof o === "object" ? safeUrl2(o.src, true) : null;
|
|
286
|
+
if (!src) return null;
|
|
287
|
+
const dim2 = (n) => {
|
|
288
|
+
const x = Math.round(Number(n));
|
|
289
|
+
return x > 0 && x <= 2e3 ? x : 0;
|
|
290
|
+
};
|
|
291
|
+
return { src, width: dim2(o.width), height: dim2(o.height) };
|
|
292
|
+
};
|
|
293
|
+
const rating = Number(raw.rating);
|
|
294
|
+
return {
|
|
295
|
+
ok: true,
|
|
296
|
+
native: {
|
|
297
|
+
title,
|
|
298
|
+
body: clip(raw.body, NATIVE_LIMITS.body),
|
|
299
|
+
sponsoredBy: clip(raw.sponsoredBy, NATIVE_LIMITS.sponsoredBy),
|
|
300
|
+
cta: clip(raw.cta, NATIVE_LIMITS.cta),
|
|
301
|
+
displayUrl: clip(raw.displayUrl, NATIVE_LIMITS.displayUrl),
|
|
302
|
+
price: clip(raw.price, NATIVE_LIMITS.price),
|
|
303
|
+
rating: Number.isFinite(rating) && rating >= 0 && rating <= 5 ? rating : null,
|
|
304
|
+
image: img(raw.image),
|
|
305
|
+
icon: img(raw.icon),
|
|
306
|
+
layout: NATIVE_LAYOUTS.includes(raw.layout) ? raw.layout : "card",
|
|
307
|
+
label: clip(raw.label, NATIVE_LIMITS.label) || "Sponsored"
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
var STYLE = `
|
|
312
|
+
:host{display:block;max-width:100%;font:inherit;color:inherit;line-height:1.35}
|
|
313
|
+
*{box-sizing:border-box}
|
|
314
|
+
.card{display:flex;flex-direction:column;gap:.5em;padding:.75em;text-decoration:none;color:inherit;border:1px solid var(--ual-native-border,rgba(128,128,128,.35));border-radius:var(--ual-native-radius,8px);background:var(--ual-native-bg,transparent);position:relative}
|
|
315
|
+
a.card:hover .title{text-decoration:underline}
|
|
316
|
+
.label{font-size:.72em;letter-spacing:.04em;text-transform:uppercase;opacity:.75}
|
|
317
|
+
.main{display:block;width:100%;height:auto;border-radius:calc(var(--ual-native-radius,8px) - 2px);background:rgba(128,128,128,.12)}
|
|
318
|
+
.row{display:flex;gap:.6em;align-items:flex-start}
|
|
319
|
+
.icon{width:2.4em;height:2.4em;flex:none;border-radius:6px;object-fit:cover}
|
|
320
|
+
.title{font-weight:700;font-size:1.05em}
|
|
321
|
+
.body{opacity:.9;font-size:.92em}
|
|
322
|
+
.meta{font-size:.8em;opacity:.7}
|
|
323
|
+
.cta{align-self:flex-start;font-weight:600;font-size:.9em;padding:.35em .8em;border-radius:999px;background:var(--ual-native-accent,#2563eb);color:var(--ual-native-accent-text,#fff)}
|
|
324
|
+
.price{font-weight:600}
|
|
325
|
+
.layout-list{flex-direction:row;align-items:flex-start}
|
|
326
|
+
.layout-list .main{width:35%;max-width:180px;flex:none}
|
|
327
|
+
.layout-list .text{display:flex;flex-direction:column;gap:.35em;min-width:0}
|
|
328
|
+
.layout-compact .main{display:none}
|
|
329
|
+
`;
|
|
330
|
+
function el(tag, cls, text) {
|
|
331
|
+
const e = document.createElement(tag);
|
|
332
|
+
if (cls) e.className = cls;
|
|
333
|
+
if (text != null) e.textContent = text;
|
|
334
|
+
return e;
|
|
335
|
+
}
|
|
336
|
+
function image(i, cls, alt) {
|
|
337
|
+
const im = el("img", cls);
|
|
338
|
+
im.src = i.src;
|
|
339
|
+
im.alt = alt || "";
|
|
340
|
+
im.loading = "lazy";
|
|
341
|
+
im.referrerPolicy = "strict-origin-when-cross-origin";
|
|
342
|
+
if (i.width && i.height) {
|
|
343
|
+
im.width = i.width;
|
|
344
|
+
im.height = i.height;
|
|
345
|
+
}
|
|
346
|
+
return im;
|
|
347
|
+
}
|
|
348
|
+
function renderNative(container, ad, { onClick } = {}) {
|
|
349
|
+
const n = ad.native;
|
|
350
|
+
const host = el("div", "ual-native");
|
|
351
|
+
const root = host.attachShadow ? host.attachShadow({ mode: "open" }) : host;
|
|
352
|
+
const style = document.createElement("style");
|
|
353
|
+
style.textContent = STYLE;
|
|
354
|
+
root.appendChild(style);
|
|
355
|
+
const card = ad.clickUrl ? el("a", `card layout-${n.layout}`) : el("div", `card layout-${n.layout}`);
|
|
356
|
+
if (ad.clickUrl) {
|
|
357
|
+
card.href = ad.clickUrl;
|
|
358
|
+
card.target = "_blank";
|
|
359
|
+
card.rel = "noopener noreferrer sponsored nofollow";
|
|
360
|
+
card.addEventListener("click", () => {
|
|
361
|
+
if (onClick) onClick();
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
card.appendChild(el("span", "label", n.label));
|
|
365
|
+
if (n.image) card.appendChild(image(n.image, "main", n.title));
|
|
366
|
+
const text = el("div", "text");
|
|
367
|
+
const head = el("div", "row");
|
|
368
|
+
if (n.icon) head.appendChild(image(n.icon, "icon", ""));
|
|
369
|
+
const titles = el("div");
|
|
370
|
+
titles.appendChild(el("div", "title", n.title));
|
|
371
|
+
if (n.sponsoredBy) titles.appendChild(el("div", "meta", n.sponsoredBy));
|
|
372
|
+
head.appendChild(titles);
|
|
373
|
+
text.appendChild(head);
|
|
374
|
+
if (n.body) text.appendChild(el("div", "body", n.body));
|
|
375
|
+
const meta = [n.displayUrl, n.rating !== null ? `\u2605 ${n.rating}` : ""].filter(Boolean).join(" \xB7 ");
|
|
376
|
+
if (meta) text.appendChild(el("div", "meta", meta));
|
|
377
|
+
if (n.price) text.appendChild(el("div", "price", n.price));
|
|
378
|
+
if (n.cta) text.appendChild(el("span", "cta", n.cta));
|
|
379
|
+
card.appendChild(text);
|
|
380
|
+
root.appendChild(card);
|
|
381
|
+
container.innerHTML = "";
|
|
382
|
+
container.appendChild(host);
|
|
383
|
+
return { el: host, destroy: () => host.remove() };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/core/video.js
|
|
387
|
+
var QUARTILES = [["firstQuartile", 0.25], ["midpoint", 0.5], ["thirdQuartile", 0.75]];
|
|
388
|
+
var QUARTILE_NAMES = ["firstQuartile", "midpoint", "thirdQuartile", "complete"];
|
|
389
|
+
var PLAY_THRESHOLD = 0.5;
|
|
390
|
+
function quartileHits(fired, currentTime, duration) {
|
|
391
|
+
if (!(duration > 0) || !(currentTime >= 0)) return [];
|
|
392
|
+
const f = currentTime / duration;
|
|
393
|
+
const hits = [];
|
|
394
|
+
for (const [name, at] of QUARTILES) {
|
|
395
|
+
if (f >= at && !fired.has(name)) {
|
|
396
|
+
fired.add(name);
|
|
397
|
+
hits.push(name);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return hits;
|
|
401
|
+
}
|
|
402
|
+
function normalizeVideo(raw, safeUrl2) {
|
|
403
|
+
const src = safeUrl2(raw.src);
|
|
404
|
+
if (!src) return { ok: false, reason: "video: src non valido (ammessi solo http e https)" };
|
|
405
|
+
return { ok: true, video: { src, poster: safeUrl2(raw.poster, true) || "", mime: typeof raw.mime === "string" && /^video\/[\w.+-]+$/.test(raw.mime) ? raw.mime : "" } };
|
|
406
|
+
}
|
|
407
|
+
function btn(label) {
|
|
408
|
+
const b = document.createElement("button");
|
|
409
|
+
b.type = "button";
|
|
410
|
+
b.textContent = label;
|
|
411
|
+
b.style.cssText = "position:absolute;right:8px;bottom:8px;z-index:2;padding:4px 10px;border:0;border-radius:999px;background:rgba(0,0,0,.65);color:#fff;font:600 12px/1.4 system-ui,sans-serif;cursor:pointer";
|
|
412
|
+
return b;
|
|
413
|
+
}
|
|
414
|
+
function renderVideo(container, ad, { onClick, onEvent, onError } = {}) {
|
|
415
|
+
const wrap = document.createElement("div");
|
|
416
|
+
wrap.className = "ual-video";
|
|
417
|
+
wrap.style.cssText = `position:relative;display:block;margin:0 auto;width:${ad.width}px;max-width:100%;aspect-ratio:${ad.width}/${ad.height};background:#000;overflow:hidden`;
|
|
418
|
+
const v = document.createElement("video");
|
|
419
|
+
v.muted = true;
|
|
420
|
+
v.defaultMuted = true;
|
|
421
|
+
v.playsInline = true;
|
|
422
|
+
v.setAttribute("playsinline", "");
|
|
423
|
+
v.preload = "metadata";
|
|
424
|
+
v.disablePictureInPicture = true;
|
|
425
|
+
v.controls = false;
|
|
426
|
+
v.style.cssText = "display:block;width:100%;height:100%;object-fit:contain";
|
|
427
|
+
if (ad.video.poster) v.poster = ad.video.poster;
|
|
428
|
+
const source = document.createElement("source");
|
|
429
|
+
source.src = ad.video.src;
|
|
430
|
+
if (ad.video.mime) source.type = ad.video.mime;
|
|
431
|
+
v.appendChild(source);
|
|
432
|
+
wrap.appendChild(v);
|
|
433
|
+
const label = document.createElement("span");
|
|
434
|
+
label.textContent = "Advertisement";
|
|
435
|
+
label.style.cssText = "position:absolute;left:8px;top:8px;z-index:2;padding:2px 6px;border-radius:4px;background:rgba(0,0,0,.55);color:#fff;font:600 11px/1.4 system-ui,sans-serif";
|
|
436
|
+
wrap.appendChild(label);
|
|
437
|
+
if (ad.clickUrl) {
|
|
438
|
+
const a = document.createElement("a");
|
|
439
|
+
a.href = ad.clickUrl;
|
|
440
|
+
a.target = "_blank";
|
|
441
|
+
a.rel = "noopener noreferrer sponsored nofollow";
|
|
442
|
+
a.setAttribute("aria-label", "Open the advertiser page");
|
|
443
|
+
a.style.cssText = "position:absolute;inset:0;z-index:1";
|
|
444
|
+
a.addEventListener("click", () => {
|
|
445
|
+
if (onClick) onClick();
|
|
446
|
+
});
|
|
447
|
+
wrap.appendChild(a);
|
|
448
|
+
}
|
|
449
|
+
const sound = btn("Sound on");
|
|
450
|
+
sound.setAttribute("aria-label", "Turn sound on");
|
|
451
|
+
sound.addEventListener("click", () => {
|
|
452
|
+
v.muted = !v.muted;
|
|
453
|
+
sound.textContent = v.muted ? "Sound on" : "Sound off";
|
|
454
|
+
sound.setAttribute("aria-label", v.muted ? "Turn sound on" : "Turn sound off");
|
|
455
|
+
});
|
|
456
|
+
wrap.appendChild(sound);
|
|
457
|
+
const trackers = ad.trackers || {};
|
|
458
|
+
const fired = /* @__PURE__ */ new Set();
|
|
459
|
+
let started = false;
|
|
460
|
+
const emit = (name, urls) => {
|
|
461
|
+
fire(urls);
|
|
462
|
+
if (onEvent) onEvent(name);
|
|
463
|
+
};
|
|
464
|
+
v.addEventListener("playing", () => {
|
|
465
|
+
if (!started) {
|
|
466
|
+
started = true;
|
|
467
|
+
emit("start", trackers.start);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
v.addEventListener("timeupdate", () => {
|
|
471
|
+
quartileHits(fired, v.currentTime, v.duration).forEach((n) => emit(n, trackers.quartile && trackers.quartile[n]));
|
|
472
|
+
});
|
|
473
|
+
v.addEventListener("ended", () => {
|
|
474
|
+
quartileHits(fired, v.duration, v.duration).forEach((n) => emit(n, trackers.quartile && trackers.quartile[n]));
|
|
475
|
+
if (!fired.has("complete")) {
|
|
476
|
+
fired.add("complete");
|
|
477
|
+
emit("complete", trackers.quartile && trackers.quartile.complete);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
v.addEventListener("error", () => {
|
|
481
|
+
if (onError) onError(new Error("video: file non riproducibile"));
|
|
482
|
+
});
|
|
483
|
+
let inView = false;
|
|
484
|
+
const sync = () => {
|
|
485
|
+
if (v.ended) return;
|
|
486
|
+
if (inView && document.visibilityState === "visible") {
|
|
487
|
+
const p = v.play();
|
|
488
|
+
if (p && p.catch) p.catch(() => {
|
|
489
|
+
});
|
|
490
|
+
} else v.pause();
|
|
491
|
+
};
|
|
492
|
+
const io = "IntersectionObserver" in window ? new IntersectionObserver((entries) => {
|
|
493
|
+
entries.forEach((en) => {
|
|
494
|
+
inView = en.intersectionRatio >= PLAY_THRESHOLD;
|
|
495
|
+
});
|
|
496
|
+
sync();
|
|
497
|
+
}, { threshold: [0, PLAY_THRESHOLD, 1] }) : null;
|
|
498
|
+
if (io) io.observe(wrap);
|
|
499
|
+
document.addEventListener("visibilitychange", sync);
|
|
500
|
+
container.innerHTML = "";
|
|
501
|
+
container.appendChild(wrap);
|
|
502
|
+
return {
|
|
503
|
+
el: wrap,
|
|
504
|
+
destroy: () => {
|
|
505
|
+
if (io) io.disconnect();
|
|
506
|
+
document.removeEventListener("visibilitychange", sync);
|
|
507
|
+
v.pause();
|
|
508
|
+
v.removeAttribute("src");
|
|
509
|
+
source.remove();
|
|
510
|
+
v.load();
|
|
511
|
+
wrap.remove();
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
275
516
|
// src/core/render.js
|
|
276
517
|
var DEFAULT_SANDBOX = "allow-scripts allow-popups allow-popups-to-escape-sandbox";
|
|
277
518
|
var MAX_DIM = 2e3;
|
|
@@ -294,12 +535,20 @@
|
|
|
294
535
|
function cleanList(list) {
|
|
295
536
|
return (Array.isArray(list) ? list : []).map((u) => safeUrl(u)).filter(Boolean);
|
|
296
537
|
}
|
|
538
|
+
function cleanCustom(custom) {
|
|
539
|
+
const out = {};
|
|
540
|
+
if (!custom || typeof custom !== "object") return out;
|
|
541
|
+
Object.keys(custom).slice(0, 20).forEach((name) => {
|
|
542
|
+
if (/^[\w-]{1,40}$/.test(name)) out[name] = cleanList(custom[name]);
|
|
543
|
+
});
|
|
544
|
+
return out;
|
|
545
|
+
}
|
|
297
546
|
function normalizeAd(raw) {
|
|
298
547
|
if (!raw || typeof raw !== "object") return { ok: false, reason: "annuncio non valido" };
|
|
299
548
|
const type = raw.type || (raw.url ? "url" : raw.src ? "image" : "html");
|
|
300
549
|
const width = dim(raw.width);
|
|
301
550
|
const height = dim(raw.height);
|
|
302
|
-
if (!width || !height) return { ok: false, reason: `dimensioni non valide (${raw.width}x${raw.height}, max ${MAX_DIM})` };
|
|
551
|
+
if (type !== "native" && (!width || !height)) return { ok: false, reason: `dimensioni non valide (${raw.width}x${raw.height}, max ${MAX_DIM})` };
|
|
303
552
|
const t = raw.trackers || {};
|
|
304
553
|
const ad = {
|
|
305
554
|
type,
|
|
@@ -307,7 +556,14 @@
|
|
|
307
556
|
height,
|
|
308
557
|
alt: raw.alt == null ? void 0 : String(raw.alt),
|
|
309
558
|
clickUrl: safeUrl(raw.clickUrl) || "",
|
|
310
|
-
trackers: {
|
|
559
|
+
trackers: {
|
|
560
|
+
impression: cleanList(t.impression),
|
|
561
|
+
viewable: cleanList(t.viewable),
|
|
562
|
+
click: cleanList(t.click),
|
|
563
|
+
start: cleanList(t.start),
|
|
564
|
+
quartile: Object.fromEntries(QUARTILE_NAMES.map((n) => [n, cleanList(t.quartile && t.quartile[n])])),
|
|
565
|
+
custom: cleanCustom(t.custom)
|
|
566
|
+
},
|
|
311
567
|
sandbox: typeof raw.sandbox === "string" ? raw.sandbox : void 0
|
|
312
568
|
};
|
|
313
569
|
if (type === "image") {
|
|
@@ -319,6 +575,14 @@
|
|
|
319
575
|
} else if (type === "html") {
|
|
320
576
|
if (typeof raw.html !== "string" || !raw.html) return { ok: false, reason: "html mancante" };
|
|
321
577
|
ad.html = raw.html;
|
|
578
|
+
} else if (type === "native") {
|
|
579
|
+
const n = normalizeNative(raw.native, safeUrl);
|
|
580
|
+
if (!n.ok) return n;
|
|
581
|
+
ad.native = n.native;
|
|
582
|
+
} else if (type === "video") {
|
|
583
|
+
const v = normalizeVideo(raw, safeUrl);
|
|
584
|
+
if (!v.ok) return v;
|
|
585
|
+
ad.video = v.video;
|
|
322
586
|
} else {
|
|
323
587
|
return { ok: false, reason: "tipo sconosciuto: " + type };
|
|
324
588
|
}
|
|
@@ -353,7 +617,8 @@ window.UALExt={
|
|
|
353
617
|
expand:function(w,h){s({cmd:'resize',w:w,h:h,expanded:true});},
|
|
354
618
|
collapse:function(){s({cmd:'collapse'});},
|
|
355
619
|
close:function(){s({cmd:'close'});},
|
|
356
|
-
click:function(){s({cmd:'click'});if(window.clickTag)window.open(window.clickTag,'_blank');}
|
|
620
|
+
click:function(){s({cmd:'click'});if(window.clickTag)window.open(window.clickTag,'_blank');},
|
|
621
|
+
track:function(n){s({cmd:'track',name:String(n)});}
|
|
357
622
|
};
|
|
358
623
|
})();<\/script>`;
|
|
359
624
|
}
|
|
@@ -396,6 +661,11 @@ window.UALExt={
|
|
|
396
661
|
fire(ad.trackers && ad.trackers.click);
|
|
397
662
|
if (onClick) onClick();
|
|
398
663
|
},
|
|
664
|
+
// UALExt.track(nome): solo i nomi dichiarati dall'ad server nella risposta
|
|
665
|
+
onTrack: (name) => {
|
|
666
|
+
const custom = ad.trackers && ad.trackers.custom;
|
|
667
|
+
if (typeof name === "string" && custom && Object.hasOwn(custom, name)) fire(custom[name]);
|
|
668
|
+
},
|
|
399
669
|
onClose
|
|
400
670
|
});
|
|
401
671
|
container.innerHTML = "";
|
|
@@ -431,12 +701,42 @@ window.UALExt={
|
|
|
431
701
|
case "click":
|
|
432
702
|
f.onClick();
|
|
433
703
|
break;
|
|
704
|
+
case "track":
|
|
705
|
+
f.onTrack(d.name);
|
|
706
|
+
break;
|
|
434
707
|
default:
|
|
435
708
|
}
|
|
436
709
|
}
|
|
437
710
|
|
|
711
|
+
// src/core/sticky.js
|
|
712
|
+
var STICKY_MAX_SCREEN_SHARE = 0.3;
|
|
713
|
+
var STICKY_POSITIONS = ["bottom", "top"];
|
|
714
|
+
function stickyAllowed(adHeight, viewportHeight) {
|
|
715
|
+
return adHeight > 0 && viewportHeight > 0 && adHeight <= viewportHeight * STICKY_MAX_SCREEN_SHARE;
|
|
716
|
+
}
|
|
717
|
+
function stickyPosition(cfg) {
|
|
718
|
+
if (!cfg) return null;
|
|
719
|
+
const p = typeof cfg === "string" ? cfg : cfg && typeof cfg === "object" ? cfg.position : "bottom";
|
|
720
|
+
return STICKY_POSITIONS.includes(p) ? p : "bottom";
|
|
721
|
+
}
|
|
722
|
+
function applySticky(el2, position, onClose) {
|
|
723
|
+
const saved = el2.style.cssText;
|
|
724
|
+
el2.style.cssText += `;position:fixed;left:0;right:0;${position}:0;z-index:2147483000;text-align:center;margin:0;padding:0`;
|
|
725
|
+
const close = document.createElement("button");
|
|
726
|
+
close.type = "button";
|
|
727
|
+
close.textContent = "\xD7";
|
|
728
|
+
close.setAttribute("aria-label", "Close advertisement");
|
|
729
|
+
close.style.cssText = `position:absolute;right:6px;${position === "bottom" ? "top" : "bottom"}:-26px;width:26px;height:26px;border:0;border-radius:6px 6px 0 0;background:rgba(0,0,0,.7);color:#fff;font:16px/26px system-ui,sans-serif;cursor:pointer`;
|
|
730
|
+
close.addEventListener("click", onClose);
|
|
731
|
+
el2.appendChild(close);
|
|
732
|
+
return () => {
|
|
733
|
+
el2.style.cssText = saved;
|
|
734
|
+
close.remove();
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
438
738
|
// src/core/viewability.js
|
|
439
|
-
function watchViewability(
|
|
739
|
+
function watchViewability(el2, { threshold = 0.5, durationMs = 1e3 } = {}, onViewable) {
|
|
440
740
|
if (!("IntersectionObserver" in window)) return () => {
|
|
441
741
|
};
|
|
442
742
|
let timer = null;
|
|
@@ -463,7 +763,7 @@ window.UALExt={
|
|
|
463
763
|
}, { threshold: [0, threshold, 1] });
|
|
464
764
|
const onVis = () => document.visibilityState === "visible" && inView ? start() : stop();
|
|
465
765
|
document.addEventListener("visibilitychange", onVis);
|
|
466
|
-
io.observe(
|
|
766
|
+
io.observe(el2);
|
|
467
767
|
function cleanup() {
|
|
468
768
|
stop();
|
|
469
769
|
io.disconnect();
|
|
@@ -557,50 +857,50 @@ window.UALExt={
|
|
|
557
857
|
function scan(root) {
|
|
558
858
|
const list = [];
|
|
559
859
|
if (root.nodeType === 1 && root.matches && root.matches("[data-ad-slot]")) list.push(root);
|
|
560
|
-
if (root.querySelectorAll) root.querySelectorAll("[data-ad-slot]").forEach((
|
|
860
|
+
if (root.querySelectorAll) root.querySelectorAll("[data-ad-slot]").forEach((el2) => list.push(el2));
|
|
561
861
|
list.forEach(maybeRegister);
|
|
562
862
|
}
|
|
563
|
-
function maybeRegister(
|
|
564
|
-
const id =
|
|
565
|
-
if (!id) return register(
|
|
863
|
+
function maybeRegister(el2) {
|
|
864
|
+
const id = el2.dataset.ualId;
|
|
865
|
+
if (!id) return register(el2);
|
|
566
866
|
if (slots.has(id)) return;
|
|
567
|
-
if (FINAL_STATUS.has(
|
|
568
|
-
resetEl(
|
|
569
|
-
register(
|
|
867
|
+
if (FINAL_STATUS.has(el2.dataset.ualStatus)) return;
|
|
868
|
+
resetEl(el2);
|
|
869
|
+
register(el2);
|
|
570
870
|
}
|
|
571
|
-
function resetEl(
|
|
572
|
-
delete
|
|
573
|
-
delete
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
871
|
+
function resetEl(el2) {
|
|
872
|
+
delete el2.dataset.ualId;
|
|
873
|
+
delete el2.dataset.ualStatus;
|
|
874
|
+
el2.querySelectorAll(":scope > .ual-label, :scope > .ual-creative").forEach((n) => n.remove());
|
|
875
|
+
el2.classList.remove("ual-slot", "ual-filled");
|
|
876
|
+
el2.style.display = "";
|
|
877
|
+
el2.style.minHeight = "";
|
|
578
878
|
}
|
|
579
|
-
function register(
|
|
580
|
-
const name =
|
|
879
|
+
function register(el2) {
|
|
880
|
+
const name = el2.getAttribute("data-ad-slot");
|
|
581
881
|
const slotCfg = cfg.slots[name];
|
|
582
|
-
|
|
882
|
+
el2.dataset.ualId = name + "#" + ++seq2;
|
|
583
883
|
if (!slotCfg) {
|
|
584
884
|
log("Slot non configurato, ignorato:", name);
|
|
585
|
-
|
|
885
|
+
el2.dataset.ualStatus = "unconfigured";
|
|
586
886
|
return;
|
|
587
887
|
}
|
|
588
888
|
if (slotCfg.enabled === false) {
|
|
589
889
|
log("Slot disabilitato:", name);
|
|
590
|
-
|
|
591
|
-
collapse(
|
|
890
|
+
el2.dataset.ualStatus = "disabled";
|
|
891
|
+
collapse(el2, slotCfg);
|
|
592
892
|
return;
|
|
593
893
|
}
|
|
594
894
|
let params = {};
|
|
595
895
|
try {
|
|
596
|
-
params =
|
|
896
|
+
params = el2.dataset.adParams ? JSON.parse(el2.dataset.adParams) : {};
|
|
597
897
|
} catch (e) {
|
|
598
|
-
warn("data-ad-params non \xE8 JSON valido",
|
|
898
|
+
warn("data-ad-params non \xE8 JSON valido", el2);
|
|
599
899
|
}
|
|
600
900
|
const slot = {
|
|
601
|
-
instanceId:
|
|
901
|
+
instanceId: el2.dataset.ualId,
|
|
602
902
|
name,
|
|
603
|
-
el,
|
|
903
|
+
el: el2,
|
|
604
904
|
cfg: slotCfg,
|
|
605
905
|
params,
|
|
606
906
|
status: "idle",
|
|
@@ -612,23 +912,23 @@ window.UALExt={
|
|
|
612
912
|
prepare(slot);
|
|
613
913
|
bus.emit("slotRegistered", publicSlot(slot));
|
|
614
914
|
const lazy = slotCfg.lazy !== void 0 ? slotCfg.lazy : cfg.lazy.enabled;
|
|
615
|
-
if (lazy && lazyIO) lazyIO.observe(
|
|
915
|
+
if (lazy && lazyIO) lazyIO.observe(el2);
|
|
616
916
|
else enqueue(slot);
|
|
617
917
|
}
|
|
618
918
|
function prepare(slot) {
|
|
619
|
-
const { el, cfg: c } = slot;
|
|
620
|
-
if (c.minHeight)
|
|
621
|
-
|
|
919
|
+
const { el: el2, cfg: c } = slot;
|
|
920
|
+
if (c.minHeight) el2.style.minHeight = c.minHeight + "px";
|
|
921
|
+
el2.classList.add("ual-slot");
|
|
622
922
|
if (c.label) {
|
|
623
923
|
const l = document.createElement("div");
|
|
624
924
|
l.className = "ual-label";
|
|
625
925
|
l.textContent = c.label;
|
|
626
926
|
l.style.cssText = "font:10px/1.4 sans-serif;color:#888;text-align:center;text-transform:uppercase;letter-spacing:.05em";
|
|
627
|
-
|
|
927
|
+
el2.appendChild(l);
|
|
628
928
|
}
|
|
629
929
|
const box = document.createElement("div");
|
|
630
930
|
box.className = "ual-creative";
|
|
631
|
-
|
|
931
|
+
el2.appendChild(box);
|
|
632
932
|
slot.box = box;
|
|
633
933
|
}
|
|
634
934
|
function observeDom() {
|
|
@@ -753,7 +1053,7 @@ window.UALExt={
|
|
|
753
1053
|
const res = await withTimeout(adapter.request(req, pcfg, c), timeout, {});
|
|
754
1054
|
group.forEach((s, i) => {
|
|
755
1055
|
const raw = res[req[i].id];
|
|
756
|
-
if (!raw || !raw.html && !raw.src && !raw.url) {
|
|
1056
|
+
if (!raw || !raw.html && !raw.src && !raw.url && !raw.native) {
|
|
757
1057
|
unfilled.push(s);
|
|
758
1058
|
return;
|
|
759
1059
|
}
|
|
@@ -765,14 +1065,18 @@ window.UALExt={
|
|
|
765
1065
|
return;
|
|
766
1066
|
}
|
|
767
1067
|
const ad = n.ad;
|
|
768
|
-
const
|
|
1068
|
+
const onClick = () => {
|
|
1069
|
+
fire(ad.trackers.click);
|
|
1070
|
+
bus.emit("slotClick", publicSlot(s));
|
|
1071
|
+
};
|
|
1072
|
+
const handle = ad.type === "native" ? renderNative(s.box, ad, { onClick }) : ad.type === "video" ? renderVideo(s.box, ad, {
|
|
1073
|
+
onClick,
|
|
1074
|
+
onEvent: (name) => bus.emit("videoEvent", { ...publicSlot(s), event: name }),
|
|
1075
|
+
onError: (error) => bus.emit("error", { provider: pid, slot: s.name, error })
|
|
1076
|
+
}) : renderInFrame(s.box, ad, {
|
|
769
1077
|
slotId: s.instanceId,
|
|
770
1078
|
onClick: () => bus.emit("slotClick", publicSlot(s)),
|
|
771
|
-
onClose: () =>
|
|
772
|
-
destroySlot(s);
|
|
773
|
-
s.el.dataset.ualStatus = "closed";
|
|
774
|
-
collapse(s.el, s.cfg, true);
|
|
775
|
-
}
|
|
1079
|
+
onClose: () => closeSlot(s)
|
|
776
1080
|
});
|
|
777
1081
|
s.cleanup.push(handle.destroy);
|
|
778
1082
|
fire(ad.trackers.impression);
|
|
@@ -799,6 +1103,14 @@ window.UALExt={
|
|
|
799
1103
|
s.el.style.display = "";
|
|
800
1104
|
bus.emit("slotRendered", { ...publicSlot(s), width: info.width, height: info.height, meta: info.meta });
|
|
801
1105
|
const vcfg = { ...cfg.viewability, ...s.cfg.viewability };
|
|
1106
|
+
if (info.ad && info.ad.type === "video" && !(s.cfg.viewability && s.cfg.viewability.durationMs !== void 0)) {
|
|
1107
|
+
vcfg.durationMs = Math.max(vcfg.durationMs, 2e3);
|
|
1108
|
+
}
|
|
1109
|
+
const pos = stickyPosition(s.cfg.sticky);
|
|
1110
|
+
if (pos && info.height) {
|
|
1111
|
+
if (stickyAllowed(info.height, window.innerHeight)) s.cleanup.push(applySticky(s.el, pos, () => closeSlot(s)));
|
|
1112
|
+
else warn(`Slot "${s.name}": sticky ignorato, l'annuncio (${info.height}px) supera il 30% dello schermo`);
|
|
1113
|
+
}
|
|
802
1114
|
const area = (info.width || 0) * (info.height || 0);
|
|
803
1115
|
if (area > LARGE_AD_AREA && !(s.cfg.viewability && s.cfg.viewability.threshold !== void 0)) {
|
|
804
1116
|
vcfg.threshold = Math.min(vcfg.threshold, 0.3);
|
|
@@ -818,11 +1130,11 @@ window.UALExt={
|
|
|
818
1130
|
collapse(s.el, s.cfg);
|
|
819
1131
|
bus.emit("slotEmpty", publicSlot(s));
|
|
820
1132
|
}
|
|
821
|
-
function collapse(
|
|
1133
|
+
function collapse(el2, c, force) {
|
|
822
1134
|
const doCollapse = force || (c.collapseEmpty !== void 0 ? c.collapseEmpty : cfg && cfg.collapseEmpty);
|
|
823
1135
|
if (doCollapse) {
|
|
824
|
-
|
|
825
|
-
|
|
1136
|
+
el2.style.display = "none";
|
|
1137
|
+
el2.style.minHeight = "";
|
|
826
1138
|
}
|
|
827
1139
|
}
|
|
828
1140
|
function clear(s) {
|
|
@@ -870,6 +1182,11 @@ window.UALExt={
|
|
|
870
1182
|
s.consentSkipped = false;
|
|
871
1183
|
enqueue(s);
|
|
872
1184
|
}
|
|
1185
|
+
function closeSlot(s) {
|
|
1186
|
+
destroySlot(s);
|
|
1187
|
+
s.el.dataset.ualStatus = "closed";
|
|
1188
|
+
collapse(s.el, s.cfg, true);
|
|
1189
|
+
}
|
|
873
1190
|
function destroySlot(s) {
|
|
874
1191
|
callDestroy(s);
|
|
875
1192
|
clear(s);
|
|
@@ -1234,7 +1551,7 @@ window.UALExt={
|
|
|
1234
1551
|
};
|
|
1235
1552
|
|
|
1236
1553
|
// src/lib.js
|
|
1237
|
-
var version = true ? "0.
|
|
1554
|
+
var version = true ? "0.2.0" : "dev";
|
|
1238
1555
|
function createUal() {
|
|
1239
1556
|
const registry = new Map(Object.entries({
|
|
1240
1557
|
json: json_default,
|