@modernconsent/widget 0.0.1
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 +21 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +761 -0
- package/dist/index.js.map +1 -0
- package/dist/mc-widget.js +413 -0
- package/dist/mc-widget.js.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
consentState,
|
|
4
|
+
servicesList,
|
|
5
|
+
hasAnswered,
|
|
6
|
+
isPanelOpen,
|
|
7
|
+
openPanel
|
|
8
|
+
} from "@modernconsent/core";
|
|
9
|
+
import { acceptAll, denyAll, setConsent } from "@modernconsent/core";
|
|
10
|
+
var LABELS = {
|
|
11
|
+
fr: {
|
|
12
|
+
bannerAcceptAll: "Tout accepter",
|
|
13
|
+
bannerDenyAll: "Continuer sans accepter",
|
|
14
|
+
bannerCustomize: "Personnaliser",
|
|
15
|
+
detailsTitle: "Personnaliser vos pr\xE9f\xE9rences",
|
|
16
|
+
detailsSubtitle: "Vous pouvez activer ou d\xE9sactiver chaque service individuellement.",
|
|
17
|
+
detailsBack: "Retour",
|
|
18
|
+
detailsAcceptAll: "Tout accepter",
|
|
19
|
+
detailsDenyAll: "Tout refuser",
|
|
20
|
+
serviceAccept: "Accepter",
|
|
21
|
+
serviceDeny: "Refuser",
|
|
22
|
+
statusAllowed: "Autoris\xE9",
|
|
23
|
+
statusDenied: "Refus\xE9",
|
|
24
|
+
statusPending: "En attente",
|
|
25
|
+
statusAlwaysActive: "Obligatoire",
|
|
26
|
+
categoryPending: "Services en attente",
|
|
27
|
+
close: "Fermer",
|
|
28
|
+
purposeAccept: "Accepter",
|
|
29
|
+
purposeDeny: "Refuser",
|
|
30
|
+
purposeStatusAllowed: "Accept\xE9e",
|
|
31
|
+
purposeStatusDenied: "Refus\xE9e",
|
|
32
|
+
purposeStatusPending: "En attente",
|
|
33
|
+
functionalTitle: "Fonctionnement du site",
|
|
34
|
+
functionalDescription: "Ces cookies et traceurs sont indispensables au fonctionnement du site, pour fournir nos services et nous assurer de leur bon fonctionnement, pour des raisons de s\xE9curit\xE9 et pour s'assurer du suivi de vos pr\xE9f\xE9rences."
|
|
35
|
+
},
|
|
36
|
+
en: {
|
|
37
|
+
bannerAcceptAll: "Accept all",
|
|
38
|
+
bannerDenyAll: "Continue without accepting",
|
|
39
|
+
bannerCustomize: "Customize",
|
|
40
|
+
detailsTitle: "Customize your preferences",
|
|
41
|
+
detailsSubtitle: "You can enable or disable each service individually.",
|
|
42
|
+
detailsBack: "Back",
|
|
43
|
+
detailsAcceptAll: "Accept all",
|
|
44
|
+
detailsDenyAll: "Deny all",
|
|
45
|
+
serviceAccept: "Accept",
|
|
46
|
+
serviceDeny: "Deny",
|
|
47
|
+
statusAllowed: "Allowed",
|
|
48
|
+
statusDenied: "Denied",
|
|
49
|
+
statusPending: "Pending",
|
|
50
|
+
statusAlwaysActive: "Always active",
|
|
51
|
+
categoryPending: "Pending services",
|
|
52
|
+
close: "Close",
|
|
53
|
+
purposeAccept: "Accept",
|
|
54
|
+
purposeDeny: "Deny",
|
|
55
|
+
purposeStatusAllowed: "Accepted",
|
|
56
|
+
purposeStatusDenied: "Denied",
|
|
57
|
+
purposeStatusPending: "Pending",
|
|
58
|
+
functionalTitle: "Site operation",
|
|
59
|
+
functionalDescription: "These cookies and trackers are essential for the site to function, to provide our services, ensure security, and remember your preferences."
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
function resolveI18n(value, lang, fallback) {
|
|
63
|
+
if (!value) return fallback;
|
|
64
|
+
if (typeof value === "string") return value;
|
|
65
|
+
return value[lang] ?? value["fr"] ?? Object.values(value)[0] ?? fallback;
|
|
66
|
+
}
|
|
67
|
+
function escapeHtml(str) {
|
|
68
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
69
|
+
}
|
|
70
|
+
var STYLES = `
|
|
71
|
+
:host {
|
|
72
|
+
--_primary: var(--mc-primary, #111827);
|
|
73
|
+
--_primary-hover: var(--mc-primary-hover, color-mix(in srgb, var(--_primary) 85%, black));
|
|
74
|
+
--_primary-text: var(--mc-primary-text, #fff);
|
|
75
|
+
--_radius: var(--mc-radius, 12px);
|
|
76
|
+
--_font: var(--mc-font, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
|
|
77
|
+
|
|
78
|
+
position: fixed;
|
|
79
|
+
inset: 0;
|
|
80
|
+
z-index: 99999;
|
|
81
|
+
font-family: var(--_font);
|
|
82
|
+
pointer-events: none;
|
|
83
|
+
display: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:host([open]) {
|
|
87
|
+
pointer-events: auto;
|
|
88
|
+
display: block;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
92
|
+
|
|
93
|
+
.backdrop {
|
|
94
|
+
position: fixed;
|
|
95
|
+
inset: 0;
|
|
96
|
+
background: rgba(0, 0, 0, 0.4);
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
animation: mc-fade-in 0.2s ease;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@keyframes mc-fade-in {
|
|
104
|
+
from { opacity: 0; }
|
|
105
|
+
to { opacity: 1; }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@keyframes mc-slide-up {
|
|
109
|
+
from { opacity: 0; transform: translateY(12px); }
|
|
110
|
+
to { opacity: 1; transform: translateY(0); }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.modal {
|
|
114
|
+
background: #ffffff;
|
|
115
|
+
border-radius: var(--_radius);
|
|
116
|
+
max-width: 640px;
|
|
117
|
+
width: calc(100% - 32px);
|
|
118
|
+
max-height: 90vh;
|
|
119
|
+
box-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.05);
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
animation: mc-slide-up 0.25s ease;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.header {
|
|
127
|
+
padding: 20px 24px 16px;
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: flex-start;
|
|
130
|
+
justify-content: space-between;
|
|
131
|
+
gap: 12px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.title {
|
|
135
|
+
font-size: 17px;
|
|
136
|
+
font-weight: 700;
|
|
137
|
+
margin: 0;
|
|
138
|
+
color: #111827;
|
|
139
|
+
letter-spacing: -0.01em;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.subtitle {
|
|
143
|
+
font-size: 13px;
|
|
144
|
+
color: #6b7280;
|
|
145
|
+
margin: 6px 0 0;
|
|
146
|
+
line-height: 1.5;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.close-btn {
|
|
150
|
+
flex-shrink: 0;
|
|
151
|
+
border: none;
|
|
152
|
+
background: #f3f4f6;
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
font-size: 16px;
|
|
155
|
+
line-height: 1;
|
|
156
|
+
padding: 6px 8px;
|
|
157
|
+
color: #6b7280;
|
|
158
|
+
border-radius: 8px;
|
|
159
|
+
transition: background 0.15s;
|
|
160
|
+
}
|
|
161
|
+
.close-btn:hover { background: #e5e7eb; }
|
|
162
|
+
|
|
163
|
+
.body {
|
|
164
|
+
padding: 0 24px 16px;
|
|
165
|
+
overflow: auto;
|
|
166
|
+
font-size: 13px;
|
|
167
|
+
color: #4b5563;
|
|
168
|
+
line-height: 1.6;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.divider {
|
|
172
|
+
height: 1px;
|
|
173
|
+
background: #e5e7eb;
|
|
174
|
+
margin: 0 24px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.footer {
|
|
178
|
+
padding: 16px 24px;
|
|
179
|
+
border-top: 1px solid #f3f4f6;
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-wrap: wrap;
|
|
182
|
+
gap: 8px;
|
|
183
|
+
justify-content: flex-end;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* \u2500\u2500\u2500 Buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
187
|
+
|
|
188
|
+
.btn {
|
|
189
|
+
appearance: none;
|
|
190
|
+
border-radius: 999px;
|
|
191
|
+
padding: 9px 18px;
|
|
192
|
+
font-size: 13px;
|
|
193
|
+
font-weight: 500;
|
|
194
|
+
border: 1px solid transparent;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
transition: all 0.15s ease;
|
|
197
|
+
white-space: nowrap;
|
|
198
|
+
letter-spacing: 0.01em;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.btn:active { transform: translateY(1px); }
|
|
202
|
+
|
|
203
|
+
.btn-primary {
|
|
204
|
+
background: var(--_primary);
|
|
205
|
+
color: var(--_primary-text);
|
|
206
|
+
border-color: var(--_primary);
|
|
207
|
+
}
|
|
208
|
+
.btn-primary:hover {
|
|
209
|
+
background: var(--_primary-hover);
|
|
210
|
+
border-color: var(--_primary-hover);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.btn-outline {
|
|
214
|
+
background: #ffffff;
|
|
215
|
+
border-color: #e5e7eb;
|
|
216
|
+
color: #374151;
|
|
217
|
+
}
|
|
218
|
+
.btn-outline:hover { background: #f9fafb; border-color: #d1d5db; }
|
|
219
|
+
|
|
220
|
+
.btn-allowed-active {
|
|
221
|
+
background: #059669;
|
|
222
|
+
border-color: #059669;
|
|
223
|
+
color: #fff;
|
|
224
|
+
}
|
|
225
|
+
.btn-allowed-active:hover { background: #047857; border-color: #047857; }
|
|
226
|
+
|
|
227
|
+
.btn-denied-active {
|
|
228
|
+
background: #dc2626;
|
|
229
|
+
border-color: #dc2626;
|
|
230
|
+
color: #fff;
|
|
231
|
+
}
|
|
232
|
+
.btn-denied-active:hover { background: #b91c1c; border-color: #b91c1c; }
|
|
233
|
+
|
|
234
|
+
.btn-ghost {
|
|
235
|
+
background: transparent;
|
|
236
|
+
color: #6b7280;
|
|
237
|
+
}
|
|
238
|
+
.btn-ghost:hover { background: #f9fafb; }
|
|
239
|
+
|
|
240
|
+
/* \u2500\u2500\u2500 Categories & Services \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
241
|
+
|
|
242
|
+
.categories {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
gap: 12px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.category {
|
|
249
|
+
border-radius: calc(var(--_radius) - 2px);
|
|
250
|
+
border: 1px solid #f3f4f6;
|
|
251
|
+
background: #fafafa;
|
|
252
|
+
padding: 14px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.category-header {
|
|
256
|
+
display: flex;
|
|
257
|
+
align-items: center;
|
|
258
|
+
justify-content: space-between;
|
|
259
|
+
margin-bottom: 8px;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.category-title {
|
|
263
|
+
font-size: 13px;
|
|
264
|
+
font-weight: 600;
|
|
265
|
+
color: #111827;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.services-list {
|
|
269
|
+
display: flex;
|
|
270
|
+
flex-direction: column;
|
|
271
|
+
gap: 6px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.service {
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: space-between;
|
|
278
|
+
gap: 12px;
|
|
279
|
+
padding: 8px 0 6px;
|
|
280
|
+
border-top: 1px solid #f3f4f6;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.service-main { flex: 1; }
|
|
284
|
+
|
|
285
|
+
.service-name {
|
|
286
|
+
font-size: 13px;
|
|
287
|
+
font-weight: 500;
|
|
288
|
+
color: #111827;
|
|
289
|
+
margin-bottom: 2px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.service-description { font-size: 12px; color: #6b7280; line-height: 1.4; }
|
|
293
|
+
|
|
294
|
+
.service-status { font-size: 11px; margin-top: 4px; font-weight: 500; }
|
|
295
|
+
.service-status.allowed { color: #059669; }
|
|
296
|
+
.service-status.denied { color: #dc2626; }
|
|
297
|
+
.service-status.pending { color: #d97706; }
|
|
298
|
+
|
|
299
|
+
.service-actions { display: flex; gap: 4px; flex-shrink: 0; }
|
|
300
|
+
|
|
301
|
+
.chip {
|
|
302
|
+
font-size: 11px;
|
|
303
|
+
padding: 3px 10px;
|
|
304
|
+
border-radius: 999px;
|
|
305
|
+
border: 1px solid #e5e7eb;
|
|
306
|
+
color: #6b7280;
|
|
307
|
+
white-space: nowrap;
|
|
308
|
+
font-weight: 500;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.chip-active {
|
|
312
|
+
background: #ecfdf5;
|
|
313
|
+
border-color: #a7f3d0;
|
|
314
|
+
color: #059669;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* \u2500\u2500\u2500 Purpose mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
318
|
+
|
|
319
|
+
.category-info {
|
|
320
|
+
display: flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
gap: 8px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.category-status {
|
|
326
|
+
font-size: 11px;
|
|
327
|
+
font-weight: 500;
|
|
328
|
+
}
|
|
329
|
+
.category-status.allowed { color: #059669; }
|
|
330
|
+
.category-status.denied { color: #dc2626; }
|
|
331
|
+
.category-status.pending { color: #d97706; }
|
|
332
|
+
|
|
333
|
+
.purpose-description {
|
|
334
|
+
font-size: 12px;
|
|
335
|
+
color: #6b7280;
|
|
336
|
+
margin: 0 0 8px;
|
|
337
|
+
line-height: 1.5;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.purpose-service-info {
|
|
341
|
+
padding: 4px 0;
|
|
342
|
+
border-top: none;
|
|
343
|
+
}
|
|
344
|
+
.purpose-service-info .service-name {
|
|
345
|
+
font-size: 12px;
|
|
346
|
+
color: #4b5563;
|
|
347
|
+
font-weight: 400;
|
|
348
|
+
}
|
|
349
|
+
.purpose-service-info .service-description {
|
|
350
|
+
font-size: 11px;
|
|
351
|
+
color: #9ca3af;
|
|
352
|
+
}
|
|
353
|
+
`;
|
|
354
|
+
var _sharedStyleSheet = null;
|
|
355
|
+
function getSharedStyleSheet() {
|
|
356
|
+
if (!_sharedStyleSheet) {
|
|
357
|
+
_sharedStyleSheet = new CSSStyleSheet();
|
|
358
|
+
_sharedStyleSheet.replaceSync(STYLES);
|
|
359
|
+
}
|
|
360
|
+
return _sharedStyleSheet;
|
|
361
|
+
}
|
|
362
|
+
var McConsentWidget = class extends HTMLElement {
|
|
363
|
+
static observedAttributes = ["lang"];
|
|
364
|
+
shadow;
|
|
365
|
+
mode = "banner";
|
|
366
|
+
_unsubscribers = [];
|
|
367
|
+
_renderScheduled = false;
|
|
368
|
+
_previouslyFocused = null;
|
|
369
|
+
_keydownHandler = null;
|
|
370
|
+
services = [];
|
|
371
|
+
consent = {};
|
|
372
|
+
answered = false;
|
|
373
|
+
panelOpen = false;
|
|
374
|
+
constructor() {
|
|
375
|
+
super();
|
|
376
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
377
|
+
}
|
|
378
|
+
get labels() {
|
|
379
|
+
const lang = this.getAttribute("lang") ?? "fr";
|
|
380
|
+
return LABELS[lang] ?? LABELS["fr"];
|
|
381
|
+
}
|
|
382
|
+
scheduleRender() {
|
|
383
|
+
if (this._renderScheduled) return;
|
|
384
|
+
this._renderScheduled = true;
|
|
385
|
+
queueMicrotask(() => {
|
|
386
|
+
this._renderScheduled = false;
|
|
387
|
+
this.render();
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
connectedCallback() {
|
|
391
|
+
this.shadow.adoptedStyleSheets = [getSharedStyleSheet()];
|
|
392
|
+
this._unsubscribers.push(
|
|
393
|
+
consentState.subscribe((consent) => {
|
|
394
|
+
this.consent = consent;
|
|
395
|
+
this.scheduleRender();
|
|
396
|
+
}),
|
|
397
|
+
hasAnswered.subscribe((answered) => {
|
|
398
|
+
this.answered = answered;
|
|
399
|
+
this.scheduleRender();
|
|
400
|
+
}),
|
|
401
|
+
servicesList.subscribe((services) => {
|
|
402
|
+
this.services = services;
|
|
403
|
+
this.ensurePanelOpenIfNeeded();
|
|
404
|
+
this.scheduleRender();
|
|
405
|
+
}),
|
|
406
|
+
isPanelOpen.subscribe((open) => {
|
|
407
|
+
const previous = this.panelOpen;
|
|
408
|
+
this.panelOpen = open;
|
|
409
|
+
if (open && !previous && this.answered) {
|
|
410
|
+
this.mode = "details";
|
|
411
|
+
}
|
|
412
|
+
this.scheduleRender();
|
|
413
|
+
})
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
disconnectedCallback() {
|
|
417
|
+
this._unsubscribers.forEach((unsub) => unsub());
|
|
418
|
+
this._unsubscribers = [];
|
|
419
|
+
this.teardownFocusTrap(false);
|
|
420
|
+
}
|
|
421
|
+
attributeChangedCallback() {
|
|
422
|
+
this.scheduleRender();
|
|
423
|
+
}
|
|
424
|
+
ensurePanelOpenIfNeeded() {
|
|
425
|
+
if (this.getPendingServices().length > 0 && !this.answered && !this.panelOpen) {
|
|
426
|
+
openPanel();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
getPendingServices() {
|
|
430
|
+
return this.services.filter((s) => s.requireConsent && this.consent[s.id] === void 0);
|
|
431
|
+
}
|
|
432
|
+
shouldShowPopup() {
|
|
433
|
+
return this.panelOpen && this.services.length > 0;
|
|
434
|
+
}
|
|
435
|
+
// ─── Focus trap ──────────────────────────────────────────────────────────────
|
|
436
|
+
setupFocusTrap() {
|
|
437
|
+
const modal = this.shadow.querySelector(".modal");
|
|
438
|
+
if (!modal) return;
|
|
439
|
+
const focusable = [
|
|
440
|
+
...modal.querySelectorAll(
|
|
441
|
+
'button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])'
|
|
442
|
+
)
|
|
443
|
+
];
|
|
444
|
+
if (!focusable.length) return;
|
|
445
|
+
if (!this._previouslyFocused) {
|
|
446
|
+
this._previouslyFocused = document.activeElement;
|
|
447
|
+
}
|
|
448
|
+
requestAnimationFrame(() => focusable[0]?.focus());
|
|
449
|
+
this._keydownHandler = (e) => {
|
|
450
|
+
if (e.key === "Escape") {
|
|
451
|
+
e.preventDefault();
|
|
452
|
+
isPanelOpen.set(false);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (e.key !== "Tab") return;
|
|
456
|
+
const active = this.shadow.activeElement;
|
|
457
|
+
const first = focusable[0];
|
|
458
|
+
const last = focusable[focusable.length - 1];
|
|
459
|
+
if (e.shiftKey && active === first) {
|
|
460
|
+
e.preventDefault();
|
|
461
|
+
last.focus();
|
|
462
|
+
} else if (!e.shiftKey && active === last) {
|
|
463
|
+
e.preventDefault();
|
|
464
|
+
first.focus();
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
document.addEventListener("keydown", this._keydownHandler);
|
|
468
|
+
}
|
|
469
|
+
teardownFocusTrap(restoreFocus = true) {
|
|
470
|
+
if (this._keydownHandler) {
|
|
471
|
+
document.removeEventListener("keydown", this._keydownHandler);
|
|
472
|
+
this._keydownHandler = null;
|
|
473
|
+
}
|
|
474
|
+
if (restoreFocus && this._previouslyFocused && "focus" in this._previouslyFocused) {
|
|
475
|
+
this._previouslyFocused.focus();
|
|
476
|
+
this._previouslyFocused = null;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// ─── Rendering ───────────────────────────────────────────────────────────────
|
|
480
|
+
render() {
|
|
481
|
+
const shouldShow = this.shouldShowPopup();
|
|
482
|
+
if (!shouldShow) {
|
|
483
|
+
this.teardownFocusTrap(true);
|
|
484
|
+
this.shadow.innerHTML = "";
|
|
485
|
+
this.removeAttribute("open");
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
this.setAttribute("open", "");
|
|
489
|
+
this.teardownFocusTrap(false);
|
|
490
|
+
if (this.mode === "banner") {
|
|
491
|
+
this.renderBanner();
|
|
492
|
+
} else {
|
|
493
|
+
const displayMode = (typeof window !== "undefined" ? window._modernConsentConfig?.displayMode : void 0) ?? "vendor";
|
|
494
|
+
if (displayMode === "purpose") {
|
|
495
|
+
this.renderPurposeDetails();
|
|
496
|
+
} else {
|
|
497
|
+
this.renderDetails();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
this.setupFocusTrap();
|
|
501
|
+
}
|
|
502
|
+
onAcceptAll = () => acceptAll();
|
|
503
|
+
onDenyAll = () => denyAll();
|
|
504
|
+
onCustomize = () => {
|
|
505
|
+
this.mode = "details";
|
|
506
|
+
this.scheduleRender();
|
|
507
|
+
};
|
|
508
|
+
onBackToBanner = () => {
|
|
509
|
+
this.mode = "banner";
|
|
510
|
+
this.scheduleRender();
|
|
511
|
+
};
|
|
512
|
+
onSetConsentForService(id, allowed) {
|
|
513
|
+
setConsent(id, allowed);
|
|
514
|
+
if (this.getPendingServices().filter((s) => s.id !== id).length === 0) {
|
|
515
|
+
isPanelOpen.set(false);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
onSetConsentForCategory(category, allowed) {
|
|
519
|
+
const servicesInCategory = this.services.filter(
|
|
520
|
+
(s) => s.category === category && s.requireConsent
|
|
521
|
+
);
|
|
522
|
+
servicesInCategory.forEach((s) => setConsent(s.id, allowed));
|
|
523
|
+
if (this.getPendingServices().length === 0) {
|
|
524
|
+
isPanelOpen.set(false);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
getCategoryStatus(services) {
|
|
528
|
+
const consentRequired = services.filter((s) => s.requireConsent);
|
|
529
|
+
if (consentRequired.length === 0) return "allowed";
|
|
530
|
+
const allAllowed = consentRequired.every((s) => this.consent[s.id]);
|
|
531
|
+
if (allAllowed) return "allowed";
|
|
532
|
+
const allDenied = consentRequired.every((s) => !this.consent[s.id]);
|
|
533
|
+
if (allDenied) return "denied";
|
|
534
|
+
return "pending";
|
|
535
|
+
}
|
|
536
|
+
groupServicesByCategory() {
|
|
537
|
+
const groups = {};
|
|
538
|
+
this.services.forEach((service) => {
|
|
539
|
+
const cat = service.category || "Autres";
|
|
540
|
+
if (!groups[cat]) groups[cat] = [];
|
|
541
|
+
groups[cat].push(service);
|
|
542
|
+
});
|
|
543
|
+
return groups;
|
|
544
|
+
}
|
|
545
|
+
renderFunctionalBlock() {
|
|
546
|
+
const show = typeof window !== "undefined" && window._modernConsentConfig?.functionalPurpose === true;
|
|
547
|
+
if (!show) return "";
|
|
548
|
+
const l = this.labels;
|
|
549
|
+
return `
|
|
550
|
+
<section class="category">
|
|
551
|
+
<div class="category-header">
|
|
552
|
+
<div class="category-info">
|
|
553
|
+
<span class="category-title">${escapeHtml(l.functionalTitle)}</span>
|
|
554
|
+
</div>
|
|
555
|
+
<span class="chip chip-active">${escapeHtml(l.statusAlwaysActive)}</span>
|
|
556
|
+
</div>
|
|
557
|
+
<div class="purpose-description">${escapeHtml(l.functionalDescription)}</div>
|
|
558
|
+
</section>
|
|
559
|
+
`;
|
|
560
|
+
}
|
|
561
|
+
renderBanner() {
|
|
562
|
+
const l = this.labels;
|
|
563
|
+
this.shadow.innerHTML = `
|
|
564
|
+
<div class="backdrop" role="dialog" aria-modal="true" aria-labelledby="mc-title">
|
|
565
|
+
<div class="modal">
|
|
566
|
+
<div class="header">
|
|
567
|
+
<div>
|
|
568
|
+
<h2 class="title" id="mc-title"><slot name="title"></slot></h2>
|
|
569
|
+
</div>
|
|
570
|
+
<button class="close-btn" type="button" aria-label="${escapeHtml(l.close)}" id="mc-close-btn">\u2715</button>
|
|
571
|
+
</div>
|
|
572
|
+
<div class="body">
|
|
573
|
+
<slot name="body"></slot>
|
|
574
|
+
</div>
|
|
575
|
+
<div class="footer">
|
|
576
|
+
<button class="btn btn-ghost" type="button" id="mc-deny-all-btn">${escapeHtml(l.bannerDenyAll)}</button>
|
|
577
|
+
<button class="btn btn-outline" type="button" id="mc-customize-btn">${escapeHtml(l.bannerCustomize)}</button>
|
|
578
|
+
<button class="btn btn-primary" type="button" id="mc-accept-all-btn">${escapeHtml(l.bannerAcceptAll)}</button>
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
`;
|
|
583
|
+
this.shadow.getElementById("mc-accept-all-btn").addEventListener("click", this.onAcceptAll);
|
|
584
|
+
this.shadow.getElementById("mc-deny-all-btn").addEventListener("click", this.onDenyAll);
|
|
585
|
+
this.shadow.getElementById("mc-customize-btn").addEventListener("click", this.onCustomize);
|
|
586
|
+
this.shadow.getElementById("mc-close-btn").addEventListener("click", () => isPanelOpen.set(false));
|
|
587
|
+
}
|
|
588
|
+
renderDetails() {
|
|
589
|
+
const l = this.labels;
|
|
590
|
+
const functionalHtml = this.renderFunctionalBlock();
|
|
591
|
+
const groups = this.groupServicesByCategory();
|
|
592
|
+
const bodyHtml = Object.keys(groups).sort().map((cat) => {
|
|
593
|
+
const services = groups[cat];
|
|
594
|
+
const hasPending = services.some((s) => s.requireConsent && this.consent[s.id] === void 0);
|
|
595
|
+
const servicesHtml = services.map((s) => {
|
|
596
|
+
if (!s.requireConsent) {
|
|
597
|
+
return `
|
|
598
|
+
<div class="service">
|
|
599
|
+
<div class="service-main">
|
|
600
|
+
<div class="service-name">${escapeHtml(s.name)}</div>
|
|
601
|
+
<div class="service-description">${escapeHtml(s.description)}</div>
|
|
602
|
+
</div>
|
|
603
|
+
<span class="chip chip-active">${escapeHtml(l.statusAlwaysActive)}</span>
|
|
604
|
+
</div>
|
|
605
|
+
`;
|
|
606
|
+
}
|
|
607
|
+
const consent = this.consent[s.id];
|
|
608
|
+
const statusLabel = consent ? l.statusAllowed : !consent ? l.statusDenied : l.statusPending;
|
|
609
|
+
const statusClass = consent ? "allowed" : !consent ? "denied" : "pending";
|
|
610
|
+
return `
|
|
611
|
+
<div class="service">
|
|
612
|
+
<div class="service-main">
|
|
613
|
+
<div class="service-name">${escapeHtml(s.name)}</div>
|
|
614
|
+
<div class="service-description">${escapeHtml(s.description)}</div>
|
|
615
|
+
<div class="service-status ${statusClass}">${escapeHtml(statusLabel)}</div>
|
|
616
|
+
</div>
|
|
617
|
+
<div class="service-actions">
|
|
618
|
+
<button class="btn btn-outline ${statusClass === "allowed" ? "btn-allowed-active" : ""}" type="button" data-action="allow" data-id="${escapeHtml(s.id)}">
|
|
619
|
+
${escapeHtml(l.serviceAccept)}
|
|
620
|
+
</button>
|
|
621
|
+
<button class="btn btn-outline ${statusClass === "denied" ? "btn-denied-active" : ""}" type="button" data-action="deny" data-id="${escapeHtml(s.id)}">
|
|
622
|
+
${escapeHtml(l.serviceDeny)}
|
|
623
|
+
</button>
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
`;
|
|
627
|
+
}).join("");
|
|
628
|
+
return `
|
|
629
|
+
<section class="category">
|
|
630
|
+
<div class="category-header">
|
|
631
|
+
<div class="category-title">${escapeHtml(cat)}</div>
|
|
632
|
+
${hasPending ? `<span class="chip">${escapeHtml(l.categoryPending)}</span>` : ""}
|
|
633
|
+
</div>
|
|
634
|
+
<div class="services-list">${servicesHtml}</div>
|
|
635
|
+
</section>
|
|
636
|
+
`;
|
|
637
|
+
}).join("");
|
|
638
|
+
this.shadow.innerHTML = `
|
|
639
|
+
<div class="backdrop" role="dialog" aria-modal="true" aria-labelledby="mc-details-title">
|
|
640
|
+
<div class="modal">
|
|
641
|
+
<div class="header">
|
|
642
|
+
<div>
|
|
643
|
+
<h2 class="title" id="mc-details-title">${escapeHtml(l.detailsTitle)}</h2>
|
|
644
|
+
<p class="subtitle">${escapeHtml(l.detailsSubtitle)}</p>
|
|
645
|
+
</div>
|
|
646
|
+
<button class="close-btn" type="button" aria-label="${escapeHtml(l.close)}" id="mc-close-btn">\u2715</button>
|
|
647
|
+
</div>
|
|
648
|
+
<div class="body">
|
|
649
|
+
<div class="categories">${functionalHtml}${bodyHtml}</div>
|
|
650
|
+
</div>
|
|
651
|
+
<div class="footer">
|
|
652
|
+
<button class="btn btn-ghost" type="button" id="mc-back-btn">${escapeHtml(l.detailsBack)}</button>
|
|
653
|
+
<button class="btn btn-outline" type="button" id="mc-deny-all-btn">${escapeHtml(l.detailsDenyAll)}</button>
|
|
654
|
+
<button class="btn btn-primary" type="button" id="mc-accept-all-btn">${escapeHtml(l.detailsAcceptAll)}</button>
|
|
655
|
+
</div>
|
|
656
|
+
</div>
|
|
657
|
+
</div>
|
|
658
|
+
`;
|
|
659
|
+
this.shadow.getElementById("mc-accept-all-btn").addEventListener("click", this.onAcceptAll);
|
|
660
|
+
this.shadow.getElementById("mc-deny-all-btn").addEventListener("click", this.onDenyAll);
|
|
661
|
+
this.shadow.getElementById("mc-back-btn").addEventListener("click", this.onBackToBanner);
|
|
662
|
+
this.shadow.getElementById("mc-close-btn").addEventListener("click", () => isPanelOpen.set(false));
|
|
663
|
+
this.shadow.querySelectorAll("[data-action]").forEach((btn) => {
|
|
664
|
+
btn.addEventListener("click", () => {
|
|
665
|
+
this.onSetConsentForService(btn.dataset.id, btn.dataset.action === "allow");
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
renderPurposeDetails() {
|
|
670
|
+
const l = this.labels;
|
|
671
|
+
const lang = this.getAttribute("lang") ?? "fr";
|
|
672
|
+
const functionalHtml = this.renderFunctionalBlock();
|
|
673
|
+
const groups = this.groupServicesByCategory();
|
|
674
|
+
const configPurposes = typeof window !== "undefined" ? window._modernConsentConfig?.purposes : void 0;
|
|
675
|
+
const bodyHtml = Object.keys(groups).sort().map((cat) => {
|
|
676
|
+
const services = groups[cat];
|
|
677
|
+
const allNonConsent = services.every((s) => !s.requireConsent);
|
|
678
|
+
const status = this.getCategoryStatus(services);
|
|
679
|
+
const vendorWithPurpose = services.find((s) => s.purposeLabel);
|
|
680
|
+
const purposeLabel = resolveI18n(
|
|
681
|
+
vendorWithPurpose?.purposeLabel,
|
|
682
|
+
lang,
|
|
683
|
+
configPurposes?.[cat]?.label ?? cat
|
|
684
|
+
);
|
|
685
|
+
const purposeDescription = resolveI18n(
|
|
686
|
+
vendorWithPurpose?.purposeDescription,
|
|
687
|
+
lang,
|
|
688
|
+
configPurposes?.[cat]?.description ?? ""
|
|
689
|
+
);
|
|
690
|
+
const actionsHtml = allNonConsent ? `<span class="chip chip-active">${escapeHtml(l.statusAlwaysActive)}</span>` : `
|
|
691
|
+
<div class="service-actions">
|
|
692
|
+
<button class="btn btn-outline ${status === "allowed" ? "btn-allowed-active" : ""}" type="button" data-category="${escapeHtml(cat)}" data-cat-action="allow">
|
|
693
|
+
${escapeHtml(l.purposeAccept)}
|
|
694
|
+
</button>
|
|
695
|
+
<button class="btn btn-outline ${status === "denied" ? "btn-denied-active" : ""}" type="button" data-category="${escapeHtml(cat)}" data-cat-action="deny">
|
|
696
|
+
${escapeHtml(l.purposeDeny)}
|
|
697
|
+
</button>
|
|
698
|
+
</div>
|
|
699
|
+
`;
|
|
700
|
+
const vendorsHtml = services.map(
|
|
701
|
+
(s) => `
|
|
702
|
+
<div class="service purpose-service-info">
|
|
703
|
+
<div class="service-main">
|
|
704
|
+
<div class="service-name">${escapeHtml(s.name)}</div>
|
|
705
|
+
<div class="service-description">${escapeHtml(s.description)}</div>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
`
|
|
709
|
+
).join("");
|
|
710
|
+
return `
|
|
711
|
+
<section class="category">
|
|
712
|
+
<div class="category-header">
|
|
713
|
+
<div class="category-info">
|
|
714
|
+
<span class="category-title">${escapeHtml(purposeLabel)}</span>
|
|
715
|
+
</div>
|
|
716
|
+
${actionsHtml}
|
|
717
|
+
</div>
|
|
718
|
+
${purposeDescription ? `<div class="purpose-description">${escapeHtml(purposeDescription)}</div>` : ""}
|
|
719
|
+
<div class="services-list">${vendorsHtml}</div>
|
|
720
|
+
</section>
|
|
721
|
+
`;
|
|
722
|
+
}).join("");
|
|
723
|
+
this.shadow.innerHTML = `
|
|
724
|
+
<div class="backdrop" role="dialog" aria-modal="true" aria-labelledby="mc-details-title">
|
|
725
|
+
<div class="modal">
|
|
726
|
+
<div class="header">
|
|
727
|
+
<div>
|
|
728
|
+
<h2 class="title" id="mc-details-title">${escapeHtml(l.detailsTitle)}</h2>
|
|
729
|
+
<p class="subtitle">${escapeHtml(l.detailsSubtitle)}</p>
|
|
730
|
+
</div>
|
|
731
|
+
<button class="close-btn" type="button" aria-label="${escapeHtml(l.close)}" id="mc-close-btn">\u2715</button>
|
|
732
|
+
</div>
|
|
733
|
+
<div class="body">
|
|
734
|
+
<div class="categories">${functionalHtml}${bodyHtml}</div>
|
|
735
|
+
</div>
|
|
736
|
+
<div class="footer">
|
|
737
|
+
<button class="btn btn-ghost" type="button" id="mc-back-btn">${escapeHtml(l.detailsBack)}</button>
|
|
738
|
+
<button class="btn btn-outline" type="button" id="mc-deny-all-btn">${escapeHtml(l.detailsDenyAll)}</button>
|
|
739
|
+
<button class="btn btn-primary" type="button" id="mc-accept-all-btn">${escapeHtml(l.detailsAcceptAll)}</button>
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
743
|
+
`;
|
|
744
|
+
this.shadow.getElementById("mc-accept-all-btn").addEventListener("click", this.onAcceptAll);
|
|
745
|
+
this.shadow.getElementById("mc-deny-all-btn").addEventListener("click", this.onDenyAll);
|
|
746
|
+
this.shadow.getElementById("mc-back-btn").addEventListener("click", this.onBackToBanner);
|
|
747
|
+
this.shadow.getElementById("mc-close-btn").addEventListener("click", () => isPanelOpen.set(false));
|
|
748
|
+
this.shadow.querySelectorAll("[data-cat-action]").forEach((btn) => {
|
|
749
|
+
btn.addEventListener("click", () => {
|
|
750
|
+
this.onSetConsentForCategory(btn.dataset.category, btn.dataset.catAction === "allow");
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
if (typeof window !== "undefined" && !customElements.get("mc-consent-widget")) {
|
|
756
|
+
customElements.define("mc-consent-widget", McConsentWidget);
|
|
757
|
+
}
|
|
758
|
+
export {
|
|
759
|
+
McConsentWidget
|
|
760
|
+
};
|
|
761
|
+
//# sourceMappingURL=index.js.map
|