astro-consent 1.0.17 → 2.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/CHANGELOG.md +38 -0
- package/LICENSE.md +1 -1
- package/README.md +221 -120
- package/dist/cli.cjs +228 -264
- package/dist/index.d.ts +10 -0
- package/dist/index.js +222 -80
- package/dist/templates/cssTemplate.cjs +447 -0
- package/dist/templates/cssTemplate.d.ts +1 -1
- package/package.json +8 -6
- package/dist/templates/cssTemplate.js +0 -117
package/dist/index.js
CHANGED
|
@@ -1,40 +1,52 @@
|
|
|
1
1
|
export default function astroConsent(options = {}) {
|
|
2
2
|
const siteName = options.siteName ?? "This website";
|
|
3
|
-
const
|
|
3
|
+
const headline = options.headline ?? `Manage cookie preferences for ${siteName}`;
|
|
4
|
+
const description = options.description ??
|
|
5
|
+
"We use cookies to improve site performance, measure traffic, and support marketing.";
|
|
6
|
+
const acceptLabel = options.acceptLabel ?? "Accept all";
|
|
7
|
+
const rejectLabel = options.rejectLabel ?? "Reject all";
|
|
8
|
+
const manageLabel = options.manageLabel ?? "Manage preferences";
|
|
9
|
+
const cookiePolicyUrl = options.cookiePolicyUrl ?? options.policyUrl ?? "/cookie-policy";
|
|
10
|
+
const privacyPolicyUrl = options.privacyPolicyUrl ?? options.policyUrl ?? "/privacy";
|
|
11
|
+
const presentation = options.presentation ?? "banner";
|
|
4
12
|
const consentDays = options.consent?.days ?? 30;
|
|
5
13
|
const storageKey = options.consent?.storageKey ?? "astro-consent";
|
|
14
|
+
const displayUntilIdle = options.displayUntilIdle ?? true;
|
|
15
|
+
const displayIdleDelayMs = options.displayIdleDelayMs ?? 1000;
|
|
6
16
|
const defaultCategories = {
|
|
7
17
|
essential: true,
|
|
8
|
-
analytics:
|
|
9
|
-
marketing:
|
|
18
|
+
analytics: true,
|
|
19
|
+
marketing: true,
|
|
10
20
|
...options.categories
|
|
11
21
|
};
|
|
12
22
|
const ttl = consentDays * 24 * 60 * 60 * 1000;
|
|
23
|
+
const stateClass = presentation === "overlay"
|
|
24
|
+
? "cb-mode-overlay"
|
|
25
|
+
: "cb-mode-banner";
|
|
13
26
|
return {
|
|
14
27
|
name: "astro-consent",
|
|
15
28
|
hooks: {
|
|
16
29
|
"astro:config:setup": ({ injectScript }) => {
|
|
17
|
-
/* ─────────────────────────────────────
|
|
18
|
-
LOAD USER CSS (required)
|
|
19
|
-
───────────────────────────────────── */
|
|
20
|
-
injectScript("head-inline", `
|
|
21
|
-
(() => {
|
|
22
|
-
const id = "astro-consent-css";
|
|
23
|
-
if (document.getElementById(id)) return;
|
|
24
|
-
const link = document.createElement("link");
|
|
25
|
-
link.id = id;
|
|
26
|
-
link.rel = "stylesheet";
|
|
27
|
-
link.href = "/src/cookiebanner.css";
|
|
28
|
-
document.head.appendChild(link);
|
|
29
|
-
})();
|
|
30
|
-
`);
|
|
31
|
-
/* ─────────────────────────────────────
|
|
32
|
-
Consent runtime (NO CSS)
|
|
33
|
-
───────────────────────────────────── */
|
|
34
30
|
injectScript("page", `
|
|
35
31
|
(() => {
|
|
36
32
|
const KEY = "${storageKey}";
|
|
37
33
|
const TTL = ${ttl};
|
|
34
|
+
const SITE_NAME = ${JSON.stringify(siteName)};
|
|
35
|
+
const HEADLINE = ${JSON.stringify(headline)};
|
|
36
|
+
const DESCRIPTION = ${JSON.stringify(description)};
|
|
37
|
+
const ACCEPT_LABEL = ${JSON.stringify(acceptLabel)};
|
|
38
|
+
const REJECT_LABEL = ${JSON.stringify(rejectLabel)};
|
|
39
|
+
const MANAGE_LABEL = ${JSON.stringify(manageLabel)};
|
|
40
|
+
const DEFAULTS = ${JSON.stringify(defaultCategories)};
|
|
41
|
+
const PRESENTATION = ${JSON.stringify(presentation)};
|
|
42
|
+
const DISPLAY_UNTIL_IDLE = ${displayUntilIdle};
|
|
43
|
+
const DISPLAY_IDLE_DELAY_MS = ${displayIdleDelayMs};
|
|
44
|
+
const STATE_CLASS = ${JSON.stringify(stateClass)};
|
|
45
|
+
const STYLE_ID = "astro-consent-css";
|
|
46
|
+
const BANNER_ID = "astro-consent-banner";
|
|
47
|
+
const MODAL_ID = "astro-consent-modal";
|
|
48
|
+
const COOKIE_POLICY_URL = ${JSON.stringify(cookiePolicyUrl)};
|
|
49
|
+
const PRIVACY_POLICY_URL = ${JSON.stringify(privacyPolicyUrl)};
|
|
38
50
|
|
|
39
51
|
function now() { return Date.now(); }
|
|
40
52
|
|
|
@@ -61,14 +73,18 @@ export default function astroConsent(options = {}) {
|
|
|
61
73
|
}));
|
|
62
74
|
}
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
function ensureApi() {
|
|
77
|
+
window.astroConsent = {
|
|
78
|
+
get: read,
|
|
79
|
+
set: write,
|
|
80
|
+
reset() {
|
|
81
|
+
localStorage.removeItem(KEY);
|
|
82
|
+
location.reload();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
ensureApi();
|
|
72
88
|
})();
|
|
73
89
|
`);
|
|
74
90
|
/* ─────────────────────────────────────
|
|
@@ -76,94 +92,220 @@ export default function astroConsent(options = {}) {
|
|
|
76
92
|
───────────────────────────────────── */
|
|
77
93
|
injectScript("page", `
|
|
78
94
|
(() => {
|
|
79
|
-
|
|
95
|
+
const SITE_NAME = ${JSON.stringify(siteName)};
|
|
96
|
+
const HEADLINE = ${JSON.stringify(headline)};
|
|
97
|
+
const DESCRIPTION = ${JSON.stringify(description)};
|
|
98
|
+
const ACCEPT_LABEL = ${JSON.stringify(acceptLabel)};
|
|
99
|
+
const REJECT_LABEL = ${JSON.stringify(rejectLabel)};
|
|
100
|
+
const MANAGE_LABEL = ${JSON.stringify(manageLabel)};
|
|
101
|
+
const COOKIE_POLICY_URL = ${JSON.stringify(cookiePolicyUrl)};
|
|
102
|
+
const PRIVACY_POLICY_URL = ${JSON.stringify(privacyPolicyUrl)};
|
|
103
|
+
const DEFAULTS = ${JSON.stringify(defaultCategories)};
|
|
104
|
+
const PRESENTATION = ${JSON.stringify(presentation)};
|
|
105
|
+
const DISPLAY_UNTIL_IDLE = ${displayUntilIdle};
|
|
106
|
+
const DISPLAY_IDLE_DELAY_MS = ${displayIdleDelayMs};
|
|
107
|
+
const STATE_CLASS = ${JSON.stringify(stateClass)};
|
|
108
|
+
const BANNER_ID = "astro-consent-banner";
|
|
109
|
+
const MODAL_ID = "astro-consent-modal";
|
|
110
|
+
const stored = window.astroConsent.get();
|
|
111
|
+
const state = stored?.categories ? { ...DEFAULTS, ...stored.categories } : { ...DEFAULTS };
|
|
112
|
+
let lastTrigger = null;
|
|
80
113
|
|
|
81
|
-
|
|
114
|
+
function start() {
|
|
115
|
+
if (window.astroConsent.get()) return;
|
|
82
116
|
|
|
83
|
-
|
|
84
|
-
|
|
117
|
+
const banner = document.createElement("div");
|
|
118
|
+
banner.id = BANNER_ID;
|
|
119
|
+
banner.className = STATE_CLASS;
|
|
120
|
+
banner.setAttribute("role", "dialog");
|
|
121
|
+
banner.setAttribute("aria-label", "Cookie consent");
|
|
122
|
+
banner.setAttribute("aria-live", "polite");
|
|
85
123
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
124
|
+
banner.innerHTML = PRESENTATION === "overlay" ? "" : \`
|
|
125
|
+
<div class="cb-container">
|
|
126
|
+
<div>
|
|
127
|
+
<div class="cb-title">\${HEADLINE}</div>
|
|
128
|
+
<div class="cb-desc">
|
|
129
|
+
\${DESCRIPTION}
|
|
130
|
+
Read our <a href="\${COOKIE_POLICY_URL}">Cookie Policy</a> and
|
|
131
|
+
<a href="\${PRIVACY_POLICY_URL}">Privacy Policy</a>.
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="cb-actions">
|
|
135
|
+
<button class="cb-manage">\${MANAGE_LABEL}</button>
|
|
136
|
+
<button class="cb-reject">\${REJECT_LABEL}</button>
|
|
137
|
+
<button class="cb-accept">\${ACCEPT_LABEL}</button>
|
|
93
138
|
</div>
|
|
94
139
|
</div>
|
|
95
|
-
|
|
96
|
-
<button class="cb-manage">Manage</button>
|
|
97
|
-
<button class="cb-reject">Reject</button>
|
|
98
|
-
<button class="cb-accept">Accept all</button>
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
\`;
|
|
102
|
-
|
|
103
|
-
document.body.appendChild(banner);
|
|
140
|
+
\`;
|
|
104
141
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
if (PRESENTATION !== "overlay") {
|
|
143
|
+
document.body.appendChild(banner);
|
|
144
|
+
requestAnimationFrame(() => banner.classList.add("cb-visible"));
|
|
145
|
+
banner.querySelector(".cb-accept").onclick = () => {
|
|
146
|
+
window.astroConsent.set({ essential: true, analytics: true, marketing: true });
|
|
147
|
+
banner.remove();
|
|
148
|
+
};
|
|
109
149
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
150
|
+
banner.querySelector(".cb-reject").onclick = () => {
|
|
151
|
+
window.astroConsent.set({ essential: true });
|
|
152
|
+
banner.remove();
|
|
153
|
+
};
|
|
114
154
|
|
|
115
|
-
|
|
155
|
+
banner.querySelector(".cb-manage").onclick = openModal;
|
|
156
|
+
}
|
|
157
|
+
if (PRESENTATION === "overlay") {
|
|
158
|
+
openModal();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
116
161
|
|
|
117
162
|
function openModal() {
|
|
163
|
+
if (document.getElementById(MODAL_ID)) return;
|
|
164
|
+
const previousOverflow = document.body.style.overflow;
|
|
165
|
+
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
166
|
+
lastTrigger = previousFocus;
|
|
167
|
+
document.body.style.overflow = "hidden";
|
|
168
|
+
|
|
118
169
|
const modal = document.createElement("div");
|
|
119
|
-
modal.id =
|
|
170
|
+
modal.id = MODAL_ID;
|
|
171
|
+
modal.setAttribute("role", "dialog");
|
|
172
|
+
modal.setAttribute("aria-modal", "true");
|
|
173
|
+
modal.setAttribute("aria-labelledby", "astro-consent-title");
|
|
120
174
|
|
|
121
175
|
modal.innerHTML = \`
|
|
122
176
|
<div class="cb-modal">
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
177
|
+
<div class="cb-modal-header">
|
|
178
|
+
<h3 id="astro-consent-title">\${HEADLINE}</h3>
|
|
179
|
+
<p>
|
|
180
|
+
\${DESCRIPTION}
|
|
181
|
+
Read our <a href="\${COOKIE_POLICY_URL}">Cookie Policy</a> and
|
|
182
|
+
<a href="\${PRIVACY_POLICY_URL}">Privacy Policy</a>.
|
|
183
|
+
</p>
|
|
128
184
|
</div>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
185
|
+
<div class="cb-panel">
|
|
186
|
+
<div class="cb-row">
|
|
187
|
+
<span>Essential</span>
|
|
188
|
+
<strong>Always on</strong>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="cb-row">
|
|
191
|
+
<span>Analytics</span>
|
|
192
|
+
<button class="cb-toggle" type="button" data-key="analytics" aria-pressed="false"></button>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="cb-row">
|
|
195
|
+
<span>Marketing</span>
|
|
196
|
+
<button class="cb-toggle" type="button" data-key="marketing" aria-pressed="false"></button>
|
|
197
|
+
</div>
|
|
133
198
|
</div>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<
|
|
137
|
-
<div class="cb-toggle" data-key="marketing"><span></span></div>
|
|
138
|
-
</div>
|
|
139
|
-
|
|
140
|
-
<div class="cb-actions">
|
|
141
|
-
<button class="cb-accept">Save preferences</button>
|
|
199
|
+
<div class="cb-actions cb-actions-modal">
|
|
200
|
+
<button class="cb-reject" type="button">\${REJECT_LABEL}</button>
|
|
201
|
+
<button class="cb-accept" type="button">\${ACCEPT_LABEL}</button>
|
|
142
202
|
</div>
|
|
143
203
|
</div>
|
|
144
204
|
\`;
|
|
145
205
|
|
|
146
206
|
document.body.appendChild(modal);
|
|
207
|
+
requestAnimationFrame(() => modal.classList.add("cb-visible"));
|
|
208
|
+
|
|
209
|
+
function closeModal(restoreFocus = true) {
|
|
210
|
+
modal.classList.remove("cb-visible");
|
|
211
|
+
modal.remove();
|
|
212
|
+
document.body.style.overflow = previousOverflow;
|
|
213
|
+
if (restoreFocus) {
|
|
214
|
+
const target = lastTrigger;
|
|
215
|
+
window.setTimeout(() => target?.focus(), 0);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function onKeyDown(event) {
|
|
220
|
+
if (event.key === "Escape") {
|
|
221
|
+
event.preventDefault();
|
|
222
|
+
closeModal();
|
|
223
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (event.key !== "Tab") return;
|
|
228
|
+
|
|
229
|
+
const focusables = Array.from(
|
|
230
|
+
modal.querySelectorAll(
|
|
231
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
232
|
+
)
|
|
233
|
+
).filter(
|
|
234
|
+
el => !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden")
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (focusables.length === 0) {
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const first = focusables[0];
|
|
243
|
+
const last = focusables[focusables.length - 1];
|
|
244
|
+
const active = document.activeElement;
|
|
245
|
+
|
|
246
|
+
if (event.shiftKey && active === first) {
|
|
247
|
+
event.preventDefault();
|
|
248
|
+
last.focus();
|
|
249
|
+
} else if (!event.shiftKey && active === last) {
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
first.focus();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
document.addEventListener("keydown", onKeyDown);
|
|
147
256
|
|
|
148
257
|
modal.querySelectorAll(".cb-toggle").forEach(toggle => {
|
|
149
258
|
const key = toggle.getAttribute("data-key");
|
|
150
|
-
|
|
259
|
+
const sync = () => {
|
|
260
|
+
const active = Boolean(state[key]);
|
|
261
|
+
toggle.classList.toggle("active", active);
|
|
262
|
+
toggle.setAttribute("aria-pressed", String(active));
|
|
263
|
+
};
|
|
264
|
+
sync();
|
|
151
265
|
|
|
152
266
|
toggle.onclick = () => {
|
|
153
267
|
state[key] = !state[key];
|
|
154
|
-
|
|
268
|
+
sync();
|
|
155
269
|
};
|
|
156
270
|
});
|
|
157
271
|
|
|
158
272
|
modal.querySelector(".cb-accept").onclick = () => {
|
|
159
273
|
window.astroConsent.set({ essential: true, ...state });
|
|
160
|
-
|
|
161
|
-
|
|
274
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
275
|
+
closeModal();
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
modal.querySelector(".cb-reject").onclick = () => {
|
|
279
|
+
window.astroConsent.set({ essential: true, analytics: false, marketing: false });
|
|
280
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
281
|
+
closeModal();
|
|
162
282
|
};
|
|
163
283
|
|
|
164
284
|
modal.onclick = e => {
|
|
165
|
-
if (e.target === modal)
|
|
285
|
+
if (e.target === modal) {
|
|
286
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
287
|
+
closeModal();
|
|
288
|
+
}
|
|
166
289
|
};
|
|
290
|
+
|
|
291
|
+
modal.querySelector(".cb-accept").focus();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function runWhenIdle() {
|
|
295
|
+
if (typeof window.requestIdleCallback === "function") {
|
|
296
|
+
window.requestIdleCallback(() => {
|
|
297
|
+
window.setTimeout(start, DISPLAY_IDLE_DELAY_MS);
|
|
298
|
+
}, { timeout: 2000 });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
window.setTimeout(start, 300 + DISPLAY_IDLE_DELAY_MS);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (DISPLAY_UNTIL_IDLE) {
|
|
306
|
+
runWhenIdle();
|
|
307
|
+
} else {
|
|
308
|
+
start();
|
|
167
309
|
}
|
|
168
310
|
})();
|
|
169
311
|
`);
|