@wu529778790/floating-ad 0.0.0-stage → 0.1.3
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 +258 -2
- package/dist/floating-ad.css +254 -0
- package/dist/floating-ad.wc.js +391 -0
- package/dist/index.d.cts +88 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.esm.js +208 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.umd.cjs +235 -0
- package/dist/index.umd.cjs.map +1 -0
- package/dist/index.umd.js +242 -0
- package/package.json +48 -4
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var FloatingAd = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
FloatingAd: () => FloatingAd,
|
|
25
|
+
default: () => index_default
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// src/FloatingAd.ts
|
|
29
|
+
var DEFAULT_THEME = {
|
|
30
|
+
bg: "rgba(255, 255, 255, 0.96)",
|
|
31
|
+
accent: "#333",
|
|
32
|
+
radius: "12px",
|
|
33
|
+
border: "rgba(0, 0, 0, 0.1)"
|
|
34
|
+
};
|
|
35
|
+
var STORAGE_KEY = "floating-ad:closed";
|
|
36
|
+
var MOBILE_QUERY = "(max-width: 767px)";
|
|
37
|
+
function isMobile() {
|
|
38
|
+
var _a, _b, _c;
|
|
39
|
+
if (typeof window === "undefined") return false;
|
|
40
|
+
return (_c = (_b = (_a = window.matchMedia) == null ? void 0 : _a.call(window, MOBILE_QUERY)) == null ? void 0 : _b.matches) != null ? _c : false;
|
|
41
|
+
}
|
|
42
|
+
function hasCloseMark() {
|
|
43
|
+
if (typeof localStorage === "undefined") return false;
|
|
44
|
+
try {
|
|
45
|
+
return localStorage.getItem(STORAGE_KEY) === "1";
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function setCloseMark() {
|
|
51
|
+
if (typeof localStorage === "undefined") return;
|
|
52
|
+
try {
|
|
53
|
+
localStorage.setItem(STORAGE_KEY, "1");
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isRenderableAd(ad) {
|
|
58
|
+
var _a;
|
|
59
|
+
return Boolean(ad && typeof ad === "object" && ((_a = ad.src) == null ? void 0 : _a.trim()));
|
|
60
|
+
}
|
|
61
|
+
function resolveOptions(options) {
|
|
62
|
+
var _a, _b, _c, _d, _e, _f;
|
|
63
|
+
const raw = Array.isArray(options.ads) ? options.ads : [];
|
|
64
|
+
return {
|
|
65
|
+
ads: raw.filter(isRenderableAd),
|
|
66
|
+
// 移动端缺省落在左下角(不挡右侧阅读区/返回顶部按钮)
|
|
67
|
+
position: (_a = options.position) != null ? _a : isMobile() ? "left-bottom" : "left-center",
|
|
68
|
+
closePersistence: (_b = options.closePersistence) != null ? _b : false,
|
|
69
|
+
hideOnMobile: (_c = options.hideOnMobile) != null ? _c : false,
|
|
70
|
+
zIndex: (_d = options.zIndex) != null ? _d : 9999,
|
|
71
|
+
adLabel: options.adLabel === void 0 ? false : options.adLabel,
|
|
72
|
+
imageFit: options.imageFit === "contain" ? "contain" : "cover",
|
|
73
|
+
sizeOverrides: {
|
|
74
|
+
width: Number.isFinite(options.width) ? Math.max(80, Number(options.width)) : void 0,
|
|
75
|
+
offset: Number.isFinite(options.offset) ? Math.max(0, Number(options.offset)) : void 0,
|
|
76
|
+
imageSize: Number.isFinite(options.imageSize) ? Math.max(40, Number(options.imageSize)) : void 0
|
|
77
|
+
},
|
|
78
|
+
theme: { ...DEFAULT_THEME, ...(_e = options.theme) != null ? _e : {} },
|
|
79
|
+
themeOverrides: (_f = options.theme) != null ? _f : {},
|
|
80
|
+
onClick: options.onClick
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
var FloatingAd = class {
|
|
84
|
+
constructor(options = {}, container = document.body) {
|
|
85
|
+
this.el = null;
|
|
86
|
+
this.closeBtn = null;
|
|
87
|
+
/** 本次渲染的广告列表(与 DOM 上的 data-index 一一对应) */
|
|
88
|
+
this.rendered = [];
|
|
89
|
+
this.handleClose = () => {
|
|
90
|
+
this.close();
|
|
91
|
+
};
|
|
92
|
+
this.handleImageError = (event) => {
|
|
93
|
+
const img = event.target;
|
|
94
|
+
if (!img || img.tagName !== "IMG" || !img.classList.contains("fa-img")) return;
|
|
95
|
+
const media = img.closest(".fa-media");
|
|
96
|
+
if (!media) return;
|
|
97
|
+
media.classList.add("fa-media--failed");
|
|
98
|
+
const item = media.closest(".fa-item");
|
|
99
|
+
if (item && !item.querySelector(".fa-body")) item.classList.add("fa-item--failed");
|
|
100
|
+
};
|
|
101
|
+
this.handleItemClick = (event) => {
|
|
102
|
+
var _a;
|
|
103
|
+
if (!this.opts.onClick) return;
|
|
104
|
+
const target = event.target;
|
|
105
|
+
const item = (_a = target == null ? void 0 : target.closest) == null ? void 0 : _a.call(target, ".fa-item");
|
|
106
|
+
if (!item) return;
|
|
107
|
+
const index = Number(item.dataset.index);
|
|
108
|
+
const ad = this.rendered[index];
|
|
109
|
+
if (!ad) return;
|
|
110
|
+
this.opts.onClick(ad, event);
|
|
111
|
+
};
|
|
112
|
+
this.opts = resolveOptions(options);
|
|
113
|
+
this.container = container;
|
|
114
|
+
if (this.opts.hideOnMobile && isMobile()) return;
|
|
115
|
+
if (this.opts.closePersistence && hasCloseMark()) return;
|
|
116
|
+
this.render();
|
|
117
|
+
}
|
|
118
|
+
/** 是否已挂载到页面 */
|
|
119
|
+
isMounted() {
|
|
120
|
+
return this.el !== null && this.el.isConnected;
|
|
121
|
+
}
|
|
122
|
+
/** 打开(已关闭后再次显示,忽略 localStorage 关闭标记) */
|
|
123
|
+
open() {
|
|
124
|
+
if (this.isMounted()) return;
|
|
125
|
+
this.render();
|
|
126
|
+
}
|
|
127
|
+
/** 关闭浮窗(完全移除)。默认不记住状态,刷新后重新出现 */
|
|
128
|
+
close() {
|
|
129
|
+
if (this.opts.closePersistence) {
|
|
130
|
+
setCloseMark();
|
|
131
|
+
}
|
|
132
|
+
this.destroy();
|
|
133
|
+
}
|
|
134
|
+
/** 从页面移除并解绑 */
|
|
135
|
+
destroy() {
|
|
136
|
+
var _a, _b, _c, _d;
|
|
137
|
+
(_a = this.closeBtn) == null ? void 0 : _a.removeEventListener("click", this.handleClose);
|
|
138
|
+
this.closeBtn = null;
|
|
139
|
+
(_b = this.el) == null ? void 0 : _b.removeEventListener("click", this.handleItemClick);
|
|
140
|
+
(_c = this.el) == null ? void 0 : _c.removeEventListener("error", this.handleImageError, true);
|
|
141
|
+
(_d = this.el) == null ? void 0 : _d.remove();
|
|
142
|
+
this.el = null;
|
|
143
|
+
this.rendered = [];
|
|
144
|
+
}
|
|
145
|
+
/** 用新配置重渲染(保留原容器) */
|
|
146
|
+
update(options) {
|
|
147
|
+
this.destroy();
|
|
148
|
+
this.opts = resolveOptions(options);
|
|
149
|
+
if (this.opts.hideOnMobile && isMobile()) return;
|
|
150
|
+
if (this.opts.closePersistence && hasCloseMark()) return;
|
|
151
|
+
this.render();
|
|
152
|
+
}
|
|
153
|
+
render() {
|
|
154
|
+
var _a;
|
|
155
|
+
const { ads, position, zIndex, themeOverrides, adLabel, imageFit, sizeOverrides } = this.opts;
|
|
156
|
+
if (ads.length === 0) return;
|
|
157
|
+
const root = document.createElement("div");
|
|
158
|
+
root.className = "fa-widget";
|
|
159
|
+
root.dataset.position = position;
|
|
160
|
+
root.style.zIndex = String(zIndex);
|
|
161
|
+
root.style.setProperty("--fa-img-fit", imageFit);
|
|
162
|
+
if (sizeOverrides.width !== void 0) {
|
|
163
|
+
root.style.setProperty("--fa-width", `${sizeOverrides.width}px`);
|
|
164
|
+
}
|
|
165
|
+
if (sizeOverrides.offset !== void 0) {
|
|
166
|
+
root.style.setProperty("--fa-offset", `${sizeOverrides.offset}px`);
|
|
167
|
+
}
|
|
168
|
+
if (sizeOverrides.imageSize !== void 0) {
|
|
169
|
+
root.style.setProperty("--fa-img-size", `${sizeOverrides.imageSize}px`);
|
|
170
|
+
}
|
|
171
|
+
const overrides = [
|
|
172
|
+
["--fa-bg", themeOverrides.bg],
|
|
173
|
+
["--fa-accent", themeOverrides.accent],
|
|
174
|
+
["--fa-radius", themeOverrides.radius],
|
|
175
|
+
["--fa-border", themeOverrides.border]
|
|
176
|
+
];
|
|
177
|
+
for (const [key, value] of overrides) {
|
|
178
|
+
if (value !== void 0) root.style.setProperty(key, value);
|
|
179
|
+
}
|
|
180
|
+
const label = adLabel ? escapeHtml(String(adLabel)) : "";
|
|
181
|
+
root.innerHTML = `
|
|
182
|
+
<button class="fa-close" type="button" aria-label="\u5173\u95ED\u5E7F\u544A">${CLOSE_SVG}</button>
|
|
183
|
+
${ads.map((ad, index) => this.renderItem(ad, index, index === 0 ? label : "")).join('<div class="fa-divider" role="separator"></div>')}
|
|
184
|
+
`;
|
|
185
|
+
this.closeBtn = root.querySelector(".fa-close");
|
|
186
|
+
(_a = this.closeBtn) == null ? void 0 : _a.addEventListener("click", this.handleClose);
|
|
187
|
+
root.addEventListener("click", this.handleItemClick);
|
|
188
|
+
root.addEventListener("error", this.handleImageError, true);
|
|
189
|
+
this.rendered = ads;
|
|
190
|
+
this.container.appendChild(root);
|
|
191
|
+
this.el = root;
|
|
192
|
+
}
|
|
193
|
+
renderItem(ad, index, label) {
|
|
194
|
+
var _a, _b;
|
|
195
|
+
const media = `<span class="fa-media">
|
|
196
|
+
<img class="fa-img" src="${escapeAttr(ad.src)}" alt="${escapeAttr((_b = (_a = ad.alt) != null ? _a : ad.title) != null ? _b : "")}" loading="lazy" decoding="async" />
|
|
197
|
+
${ad.badge ? `<span class="fa-tag">${escapeHtml(ad.badge)}</span>` : ""}
|
|
198
|
+
${label ? `<span class="fa-label">${label}</span>` : ""}
|
|
199
|
+
</span>`;
|
|
200
|
+
const body = ad.title || ad.desc ? `<span class="fa-body">
|
|
201
|
+
${ad.title ? `<span class="fa-title">${escapeHtml(ad.title)}</span>` : ""}
|
|
202
|
+
${ad.desc ? `<span class="fa-desc">${escapeHtml(ad.desc)}</span>` : ""}
|
|
203
|
+
</span>` : "";
|
|
204
|
+
const inner = `${media}${body}`;
|
|
205
|
+
const href = ad.href ? safeHref(ad.href) : null;
|
|
206
|
+
if (href) {
|
|
207
|
+
const target = ad.target === "_self" ? "_self" : "_blank";
|
|
208
|
+
const rel = target === "_blank" ? ' rel="noopener noreferrer nofollow sponsored"' : ' rel="nofollow sponsored"';
|
|
209
|
+
return `<a class="fa-item" href="${escapeAttr(href)}" target="${target}"${rel} data-index="${index}">${inner}</a>`;
|
|
210
|
+
}
|
|
211
|
+
return `<div class="fa-item" data-index="${index}">${inner}</div>`;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var CLOSE_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>';
|
|
215
|
+
function safeHref(href) {
|
|
216
|
+
const trimmed = href.trim();
|
|
217
|
+
if (!trimmed) return null;
|
|
218
|
+
const compact = trimmed.replace(/[\s\u0000-\u001f]/g, "").toLowerCase();
|
|
219
|
+
if (/^(javascript|data|vbscript|file):/.test(compact)) return null;
|
|
220
|
+
return trimmed;
|
|
221
|
+
}
|
|
222
|
+
function escapeHtml(value) {
|
|
223
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
224
|
+
}
|
|
225
|
+
function escapeAttr(value) {
|
|
226
|
+
return escapeHtml(value);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/index.ts
|
|
230
|
+
var index_default = FloatingAd;
|
|
231
|
+
return __toCommonJS(index_exports);
|
|
232
|
+
})();
|
|
233
|
+
|
|
234
|
+
if (typeof FloatingAd !== "undefined") {
|
|
235
|
+
FloatingAd = FloatingAd.default || FloatingAd;
|
|
236
|
+
var faDoc = document.documentElement;
|
|
237
|
+
var faDisabled = faDoc && faDoc.getAttribute("data-fa-auto") === "false";
|
|
238
|
+
if (!faDisabled && window.__FLOATING_AD_OPTIONS__ && !window.__floatingAdAutoInit__) {
|
|
239
|
+
window.__floatingAdAutoInit__ = true;
|
|
240
|
+
new FloatingAd(window.__FLOATING_AD_OPTIONS__);
|
|
241
|
+
}
|
|
242
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu529778790/floating-ad",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "A minimal, zero-dependency floating ad slot widget for any website: image or text ads on the left/right edge, closable, themeable via CSS variables.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.umd.cjs",
|
|
7
|
+
"module": "./dist/index.esm.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.esm.js",
|
|
13
|
+
"require": "./dist/index.umd.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./style.css": "./dist/floating-ad.css"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"floating",
|
|
22
|
+
"widget",
|
|
23
|
+
"ad",
|
|
24
|
+
"ads",
|
|
25
|
+
"adslot",
|
|
26
|
+
"banner",
|
|
27
|
+
"promotion",
|
|
28
|
+
"sponsor"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/wu529778790/components.git"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"sideEffects": [
|
|
39
|
+
"*.css"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup && node -e \"require('fs').copyFileSync('src/styles.css','dist/floating-ad.css')\"",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"typecheck": "tsc --noEmit"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"tsup": "^8.3.5",
|
|
48
|
+
"typescript": "^5.6.3"
|
|
49
|
+
}
|
|
50
|
+
}
|