cookiecraft 1.0.7 → 1.0.9
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 +171 -304
- package/dist/cookiecraft.css +1 -1
- package/dist/cookiecraft.esm.js +332 -117
- package/dist/cookiecraft.esm.js.map +1 -1
- package/dist/cookiecraft.js +332 -117
- package/dist/cookiecraft.js.map +1 -1
- package/dist/cookiecraft.min.js +1 -1
- package/dist/cookiecraft.min.js.map +1 -1
- package/dist/stats.html +1 -1
- package/dist/types/blocking/CategoryManager.d.ts +1 -0
- package/dist/types/core/ConsentManager.d.ts +1 -1
- package/dist/types/core/CookieConsent.d.ts +5 -4
- package/dist/types/core/EventEmitter.d.ts +3 -2
- package/dist/types/i18n/translations.d.ts +6 -0
- package/dist/types/integrations/GTMConsentMode.d.ts +0 -6
- package/dist/types/types/index.d.ts +3 -7
- package/dist/types/ui/Banner.d.ts +6 -0
- package/dist/types/ui/PreferenceCenter.d.ts +1 -0
- package/package.json +4 -5
package/dist/cookiecraft.esm.js
CHANGED
|
@@ -43,13 +43,20 @@ class StorageManager {
|
|
|
43
43
|
* Clear consent record from localStorage
|
|
44
44
|
*/
|
|
45
45
|
clear() {
|
|
46
|
-
|
|
46
|
+
try {
|
|
47
|
+
localStorage.removeItem(StorageManager.STORAGE_KEY);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
console.error('Failed to clear consent:', e);
|
|
51
|
+
}
|
|
47
52
|
}
|
|
48
53
|
/**
|
|
49
54
|
* Check if consent record has expired
|
|
50
55
|
*/
|
|
51
56
|
isExpired(consent) {
|
|
52
57
|
const expiry = new Date(consent.expiresAt);
|
|
58
|
+
if (isNaN(expiry.getTime()))
|
|
59
|
+
return true;
|
|
53
60
|
return expiry < new Date();
|
|
54
61
|
}
|
|
55
62
|
/**
|
|
@@ -60,7 +67,6 @@ class StorageManager {
|
|
|
60
67
|
typeof data.version === 'number' &&
|
|
61
68
|
typeof data.timestamp === 'string' &&
|
|
62
69
|
typeof data.categories === 'object' &&
|
|
63
|
-
typeof data.userAgent === 'string' &&
|
|
64
70
|
typeof data.expiresAt === 'string');
|
|
65
71
|
}
|
|
66
72
|
/**
|
|
@@ -76,11 +82,20 @@ class StorageManager {
|
|
|
76
82
|
const now = new Date();
|
|
77
83
|
const expiryDate = new Date(now);
|
|
78
84
|
expiryDate.setMonth(expiryDate.getMonth() + StorageManager.EXPIRY_MONTHS);
|
|
85
|
+
// Coerce category values to booleans
|
|
86
|
+
const rawCategories = record.categories;
|
|
87
|
+
const categories = {
|
|
88
|
+
necessary: rawCategories.necessary === true,
|
|
89
|
+
analytics: rawCategories.analytics === true,
|
|
90
|
+
marketing: rawCategories.marketing === true,
|
|
91
|
+
};
|
|
92
|
+
if ('preferences' in rawCategories) {
|
|
93
|
+
categories.preferences = rawCategories.preferences === true;
|
|
94
|
+
}
|
|
79
95
|
return {
|
|
80
96
|
version: typeof record.version === 'number' ? record.version : 1,
|
|
81
97
|
timestamp: typeof record.timestamp === 'string' ? record.timestamp : now.toISOString(),
|
|
82
|
-
categories
|
|
83
|
-
userAgent: typeof record.userAgent === 'string' ? record.userAgent : navigator.userAgent,
|
|
98
|
+
categories,
|
|
84
99
|
expiresAt: typeof record.expiresAt === 'string' ? record.expiresAt : expiryDate.toISOString(),
|
|
85
100
|
};
|
|
86
101
|
}
|
|
@@ -95,6 +110,7 @@ StorageManager.EXPIRY_MONTHS = 13;
|
|
|
95
110
|
*/
|
|
96
111
|
class ConsentManager {
|
|
97
112
|
constructor(config) {
|
|
113
|
+
this.consent = null;
|
|
98
114
|
this.config = config;
|
|
99
115
|
}
|
|
100
116
|
/**
|
|
@@ -113,6 +129,10 @@ class ConsentManager {
|
|
|
113
129
|
}
|
|
114
130
|
}
|
|
115
131
|
}
|
|
132
|
+
// Coerce all values to booleans
|
|
133
|
+
for (const key of Object.keys(categories)) {
|
|
134
|
+
categories[key] = categories[key] === true;
|
|
135
|
+
}
|
|
116
136
|
return true;
|
|
117
137
|
}
|
|
118
138
|
/**
|
|
@@ -129,13 +149,12 @@ class ConsentManager {
|
|
|
129
149
|
* Check if user needs to give consent
|
|
130
150
|
*/
|
|
131
151
|
needsConsent() {
|
|
132
|
-
return this.consent ===
|
|
152
|
+
return this.consent === null;
|
|
133
153
|
}
|
|
134
154
|
/**
|
|
135
155
|
* Check if stored consent needs update due to policy change
|
|
136
156
|
*/
|
|
137
157
|
needsUpdate(storedConsent) {
|
|
138
|
-
// Check if policy version has changed
|
|
139
158
|
return storedConsent.version < this.config.revision;
|
|
140
159
|
}
|
|
141
160
|
/**
|
|
@@ -155,7 +174,6 @@ class ConsentManager {
|
|
|
155
174
|
version: this.config.revision,
|
|
156
175
|
timestamp: now.toISOString(),
|
|
157
176
|
categories: Object.assign({}, categories),
|
|
158
|
-
userAgent: navigator.userAgent,
|
|
159
177
|
expiresAt: expiryDate.toISOString(),
|
|
160
178
|
};
|
|
161
179
|
}
|
|
@@ -396,7 +414,6 @@ class ScriptBlocker {
|
|
|
396
414
|
class CategoryManager {
|
|
397
415
|
constructor() {
|
|
398
416
|
this.categories = new Map();
|
|
399
|
-
// Initialize with common patterns
|
|
400
417
|
this.initializeDefaultPatterns();
|
|
401
418
|
}
|
|
402
419
|
/**
|
|
@@ -433,11 +450,11 @@ class CategoryManager {
|
|
|
433
450
|
}
|
|
434
451
|
/**
|
|
435
452
|
* Initialize default URL patterns for common tracking services
|
|
453
|
+
* Note: GTM is NOT auto-categorized — it should be managed via GTM Consent Mode v2
|
|
436
454
|
*/
|
|
437
455
|
initializeDefaultPatterns() {
|
|
438
456
|
this.categories.set('analytics', [
|
|
439
457
|
'google-analytics.com',
|
|
440
|
-
'googletagmanager.com',
|
|
441
458
|
'analytics.google.com',
|
|
442
459
|
'plausible.io',
|
|
443
460
|
'matomo.org',
|
|
@@ -447,6 +464,7 @@ class CategoryManager {
|
|
|
447
464
|
'amplitude.com',
|
|
448
465
|
]);
|
|
449
466
|
this.categories.set('marketing', [
|
|
467
|
+
'googletagmanager.com',
|
|
450
468
|
'facebook.net',
|
|
451
469
|
'facebook.com/tr',
|
|
452
470
|
'connect.facebook.net',
|
|
@@ -458,6 +476,7 @@ class CategoryManager {
|
|
|
458
476
|
'adroll.com',
|
|
459
477
|
'taboola.com',
|
|
460
478
|
'outbrain.com',
|
|
479
|
+
'tiktok.com',
|
|
461
480
|
]);
|
|
462
481
|
this.categories.set('necessary', []);
|
|
463
482
|
}
|
|
@@ -508,18 +527,47 @@ function sanitizeColor(color) {
|
|
|
508
527
|
// Allow hsl/hsla
|
|
509
528
|
if (/^hsla?\(\s*[\d\s,./%deg]+\)$/.test(trimmed))
|
|
510
529
|
return trimmed;
|
|
511
|
-
// Allow CSS named colors (basic set)
|
|
512
|
-
|
|
530
|
+
// Allow CSS named colors (basic set) but block CSS keywords that could be abused
|
|
531
|
+
const CSS_KEYWORDS = ['inherit', 'initial', 'unset', 'revert', 'revert-layer'];
|
|
532
|
+
if (/^[a-zA-Z]+$/.test(trimmed) && !CSS_KEYWORDS.includes(trimmed.toLowerCase())) {
|
|
513
533
|
return trimmed;
|
|
534
|
+
}
|
|
514
535
|
return '';
|
|
515
536
|
}
|
|
516
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Normalize any supported color format to 6-digit hex
|
|
540
|
+
* Supports: #RGB, #RRGGBB, #RRGGBBAA, named colors
|
|
541
|
+
* Returns null if conversion fails
|
|
542
|
+
*/
|
|
543
|
+
function normalizeToHex6(color) {
|
|
544
|
+
const trimmed = color.trim();
|
|
545
|
+
// Already 6-digit hex
|
|
546
|
+
if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) {
|
|
547
|
+
return trimmed;
|
|
548
|
+
}
|
|
549
|
+
// 3-digit hex → expand to 6-digit
|
|
550
|
+
if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {
|
|
551
|
+
const r = trimmed[1];
|
|
552
|
+
const g = trimmed[2];
|
|
553
|
+
const b = trimmed[3];
|
|
554
|
+
return `#${r}${r}${g}${g}${b}${b}`;
|
|
555
|
+
}
|
|
556
|
+
// 8-digit hex (with alpha) → strip alpha
|
|
557
|
+
if (/^#[0-9a-fA-F]{8}$/.test(trimmed)) {
|
|
558
|
+
return trimmed.substring(0, 7);
|
|
559
|
+
}
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
517
562
|
/**
|
|
518
563
|
* Adjust a hex color brightness by a percentage
|
|
519
564
|
* Negative = darker, positive = lighter
|
|
520
565
|
*/
|
|
521
566
|
function adjustColorBrightness(color, percent) {
|
|
522
|
-
const
|
|
567
|
+
const hex6 = normalizeToHex6(color);
|
|
568
|
+
if (!hex6)
|
|
569
|
+
return color;
|
|
570
|
+
const hex = hex6.replace('#', '');
|
|
523
571
|
const r = parseInt(hex.substring(0, 2), 16);
|
|
524
572
|
const g = parseInt(hex.substring(2, 4), 16);
|
|
525
573
|
const b = parseInt(hex.substring(4, 6), 16);
|
|
@@ -539,8 +587,13 @@ function adjustColorBrightness(color, percent) {
|
|
|
539
587
|
function buildColorStyle(safeColor) {
|
|
540
588
|
if (!safeColor)
|
|
541
589
|
return '';
|
|
542
|
-
|
|
543
|
-
|
|
590
|
+
// Only generate hover color for hex colors
|
|
591
|
+
const hex6 = normalizeToHex6(safeColor);
|
|
592
|
+
if (!hex6) {
|
|
593
|
+
return `--cc-primary: ${safeColor};`;
|
|
594
|
+
}
|
|
595
|
+
const hover = adjustColorBrightness(hex6, -15);
|
|
596
|
+
return `--cc-primary: ${hex6}; --cc-primary-hover: ${hover};`;
|
|
544
597
|
}
|
|
545
598
|
|
|
546
599
|
/**
|
|
@@ -549,6 +602,8 @@ function buildColorStyle(safeColor) {
|
|
|
549
602
|
class Banner {
|
|
550
603
|
constructor(config, eventEmitter) {
|
|
551
604
|
this.element = null;
|
|
605
|
+
this.hideTimeout = null;
|
|
606
|
+
this.previousActiveElement = null;
|
|
552
607
|
this.config = config;
|
|
553
608
|
this.eventEmitter = eventEmitter;
|
|
554
609
|
}
|
|
@@ -556,8 +611,14 @@ class Banner {
|
|
|
556
611
|
* Show the banner
|
|
557
612
|
*/
|
|
558
613
|
show() {
|
|
614
|
+
// Clear any pending hide timeout
|
|
615
|
+
if (this.hideTimeout) {
|
|
616
|
+
clearTimeout(this.hideTimeout);
|
|
617
|
+
this.hideTimeout = null;
|
|
618
|
+
}
|
|
559
619
|
const append = () => {
|
|
560
620
|
if (!this.element) {
|
|
621
|
+
this.previousActiveElement = document.activeElement;
|
|
561
622
|
this.element = this.createDOM();
|
|
562
623
|
document.body.appendChild(this.element);
|
|
563
624
|
this.attachListeners();
|
|
@@ -570,9 +631,9 @@ class Banner {
|
|
|
570
631
|
// Disable page interaction if configured
|
|
571
632
|
if (this.config.disablePageInteraction) {
|
|
572
633
|
document.body.style.overflow = 'hidden';
|
|
634
|
+
this.trapFocus();
|
|
573
635
|
}
|
|
574
636
|
};
|
|
575
|
-
// Wait for body if not yet available
|
|
576
637
|
if (!document.body) {
|
|
577
638
|
document.addEventListener('DOMContentLoaded', append);
|
|
578
639
|
return;
|
|
@@ -585,22 +646,30 @@ class Banner {
|
|
|
585
646
|
hide() {
|
|
586
647
|
var _a;
|
|
587
648
|
(_a = this.element) === null || _a === void 0 ? void 0 : _a.classList.remove('is-visible');
|
|
588
|
-
// Re-enable page interaction
|
|
589
649
|
if (this.config.disablePageInteraction) {
|
|
590
650
|
document.body.style.overflow = '';
|
|
591
651
|
}
|
|
592
|
-
setTimeout(() => {
|
|
652
|
+
this.hideTimeout = setTimeout(() => {
|
|
593
653
|
this.destroy();
|
|
594
|
-
}, 300);
|
|
654
|
+
}, 300);
|
|
595
655
|
}
|
|
596
656
|
/**
|
|
597
657
|
* Destroy the banner
|
|
598
658
|
*/
|
|
599
659
|
destroy() {
|
|
660
|
+
if (this.hideTimeout) {
|
|
661
|
+
clearTimeout(this.hideTimeout);
|
|
662
|
+
this.hideTimeout = null;
|
|
663
|
+
}
|
|
600
664
|
if (this.element) {
|
|
601
665
|
this.element.remove();
|
|
602
666
|
this.element = null;
|
|
603
667
|
}
|
|
668
|
+
// Restore focus
|
|
669
|
+
if (this.previousActiveElement && document.contains(this.previousActiveElement)) {
|
|
670
|
+
this.previousActiveElement.focus();
|
|
671
|
+
this.previousActiveElement = null;
|
|
672
|
+
}
|
|
604
673
|
}
|
|
605
674
|
/**
|
|
606
675
|
* Create DOM structure for banner
|
|
@@ -611,12 +680,14 @@ class Banner {
|
|
|
611
680
|
const position = this.config.position || 'bottom';
|
|
612
681
|
const layout = this.config.layout || 'bar';
|
|
613
682
|
const backdropBlur = this.config.backdropBlur !== false;
|
|
683
|
+
const isModal = this.config.disablePageInteraction;
|
|
614
684
|
const safeColor = this.config.primaryColor ? sanitizeColor(this.config.primaryColor) : '';
|
|
615
685
|
const colorStyle = buildColorStyle(safeColor);
|
|
616
686
|
const template = `
|
|
617
687
|
<div
|
|
618
688
|
class="cc-banner cc-banner--${escapeHtml(position)} cc-banner--${escapeHtml(layout)} ${backdropBlur ? 'cc-backdrop-blur' : ''}"
|
|
619
|
-
role="region"
|
|
689
|
+
role="${isModal ? 'dialog' : 'region'}"
|
|
690
|
+
${isModal ? 'aria-modal="true"' : ''}
|
|
620
691
|
aria-label="Cookie consent"
|
|
621
692
|
aria-live="polite"
|
|
622
693
|
data-theme="${escapeHtml(theme)}"
|
|
@@ -625,7 +696,7 @@ class Banner {
|
|
|
625
696
|
<div class="cc-banner__container">
|
|
626
697
|
<div class="cc-banner__content">
|
|
627
698
|
<h2 class="cc-banner__title">
|
|
628
|
-
${escapeHtml(translations.title || '
|
|
699
|
+
${escapeHtml(translations.title || 'We use cookies')}
|
|
629
700
|
</h2>
|
|
630
701
|
<p class="cc-banner__description">
|
|
631
702
|
${this.getDescriptionHTML()}
|
|
@@ -635,23 +706,23 @@ class Banner {
|
|
|
635
706
|
<button
|
|
636
707
|
class="cc-btn cc-btn--ghost"
|
|
637
708
|
data-action="reject"
|
|
638
|
-
aria-label="${escapeHtml(translations.rejectAll || '
|
|
709
|
+
aria-label="${escapeHtml(translations.rejectAll || 'Essentials only')}"
|
|
639
710
|
>
|
|
640
|
-
${escapeHtml(translations.rejectAll || '
|
|
711
|
+
${escapeHtml(translations.rejectAll || 'Essentials only')}
|
|
641
712
|
</button>
|
|
642
713
|
<button
|
|
643
714
|
class="cc-btn cc-btn--tertiary"
|
|
644
715
|
data-action="customize"
|
|
645
|
-
aria-label="${escapeHtml(translations.customize || '
|
|
716
|
+
aria-label="${escapeHtml(translations.customize || 'Customize')}"
|
|
646
717
|
>
|
|
647
|
-
${escapeHtml(translations.customize || '
|
|
718
|
+
${escapeHtml(translations.customize || 'Customize')}
|
|
648
719
|
</button>
|
|
649
720
|
<button
|
|
650
721
|
class="cc-btn cc-btn--accept"
|
|
651
722
|
data-action="accept"
|
|
652
|
-
aria-label="${escapeHtml(translations.acceptAll || '
|
|
723
|
+
aria-label="${escapeHtml(translations.acceptAll || 'Accept all')}"
|
|
653
724
|
>
|
|
654
|
-
${escapeHtml(translations.acceptAll || '
|
|
725
|
+
${escapeHtml(translations.acceptAll || 'Accept all')}
|
|
655
726
|
</button>
|
|
656
727
|
</div>
|
|
657
728
|
</div>
|
|
@@ -683,10 +754,8 @@ class Banner {
|
|
|
683
754
|
break;
|
|
684
755
|
}
|
|
685
756
|
});
|
|
686
|
-
// Keyboard support
|
|
687
757
|
(_b = this.element) === null || _b === void 0 ? void 0 : _b.addEventListener('keydown', (e) => {
|
|
688
758
|
if (e.key === 'Escape' && this.config.disablePageInteraction) {
|
|
689
|
-
// Allow ESC to close if page interaction is disabled
|
|
690
759
|
this.handleRejectAll();
|
|
691
760
|
}
|
|
692
761
|
});
|
|
@@ -695,36 +764,24 @@ class Banner {
|
|
|
695
764
|
* Handle accept all action
|
|
696
765
|
*/
|
|
697
766
|
handleAcceptAll() {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
marketing: true,
|
|
703
|
-
};
|
|
704
|
-
// Only add preferences if it's configured
|
|
705
|
-
if ((_a = this.config.categories) === null || _a === void 0 ? void 0 : _a.preferences) {
|
|
706
|
-
allCategories.preferences = true;
|
|
767
|
+
const allCategories = { necessary: true, analytics: true, marketing: true };
|
|
768
|
+
// Add all configured categories
|
|
769
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
770
|
+
allCategories[key] = true;
|
|
707
771
|
}
|
|
708
772
|
this.eventEmitter.emit('consent:accept', allCategories);
|
|
709
|
-
(_c = (_b = this.config).onAccept) === null || _c === void 0 ? void 0 : _c.call(_b, allCategories);
|
|
710
773
|
this.hide();
|
|
711
774
|
}
|
|
712
775
|
/**
|
|
713
776
|
* Handle reject all action
|
|
714
777
|
*/
|
|
715
778
|
handleRejectAll() {
|
|
716
|
-
|
|
717
|
-
const
|
|
718
|
-
necessary
|
|
719
|
-
|
|
720
|
-
marketing: false,
|
|
721
|
-
};
|
|
722
|
-
// Only add preferences if it's configured
|
|
723
|
-
if ((_a = this.config.categories) === null || _a === void 0 ? void 0 : _a.preferences) {
|
|
724
|
-
necessaryOnly.preferences = false;
|
|
779
|
+
const necessaryOnly = { necessary: true, analytics: false, marketing: false };
|
|
780
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
781
|
+
if (key !== 'necessary')
|
|
782
|
+
necessaryOnly[key] = false;
|
|
725
783
|
}
|
|
726
784
|
this.eventEmitter.emit('consent:reject', necessaryOnly);
|
|
727
|
-
(_c = (_b = this.config).onReject) === null || _c === void 0 ? void 0 : _c.call(_b);
|
|
728
785
|
this.hide();
|
|
729
786
|
}
|
|
730
787
|
/**
|
|
@@ -734,17 +791,41 @@ class Banner {
|
|
|
734
791
|
this.eventEmitter.emit('preferences:show');
|
|
735
792
|
this.hide();
|
|
736
793
|
}
|
|
794
|
+
/**
|
|
795
|
+
* Trap focus within banner (when disablePageInteraction is true)
|
|
796
|
+
*/
|
|
797
|
+
trapFocus() {
|
|
798
|
+
var _a, _b;
|
|
799
|
+
const focusableElements = (_a = this.element) === null || _a === void 0 ? void 0 : _a.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
800
|
+
if (!focusableElements || focusableElements.length === 0)
|
|
801
|
+
return;
|
|
802
|
+
const firstFocusable = focusableElements[0];
|
|
803
|
+
const lastFocusable = focusableElements[focusableElements.length - 1];
|
|
804
|
+
firstFocusable === null || firstFocusable === void 0 ? void 0 : firstFocusable.focus();
|
|
805
|
+
(_b = this.element) === null || _b === void 0 ? void 0 : _b.addEventListener('keydown', (e) => {
|
|
806
|
+
if (e.key === 'Tab') {
|
|
807
|
+
if (e.shiftKey && document.activeElement === firstFocusable) {
|
|
808
|
+
e.preventDefault();
|
|
809
|
+
lastFocusable.focus();
|
|
810
|
+
}
|
|
811
|
+
else if (!e.shiftKey && document.activeElement === lastFocusable) {
|
|
812
|
+
e.preventDefault();
|
|
813
|
+
firstFocusable.focus();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
}
|
|
737
818
|
/**
|
|
738
819
|
* Generate description HTML with privacy policy link
|
|
739
820
|
*/
|
|
740
821
|
getDescriptionHTML() {
|
|
741
822
|
const translations = this.config.translations || {};
|
|
742
|
-
const defaultDescription = '
|
|
823
|
+
const defaultDescription = 'We use cookies to improve your experience on our site. You can choose which cookies you accept.';
|
|
743
824
|
const description = escapeHtml(translations.description || defaultDescription);
|
|
744
825
|
if (translations.privacyPolicyUrl) {
|
|
745
826
|
const safeUrl = sanitizeUrl(translations.privacyPolicyUrl);
|
|
746
827
|
if (safeUrl) {
|
|
747
|
-
const linkLabel = escapeHtml(translations.privacyPolicyLabel || '
|
|
828
|
+
const linkLabel = escapeHtml(translations.privacyPolicyLabel || 'Privacy Policy');
|
|
748
829
|
return `${description} <a href="${safeUrl}" target="_blank" rel="noopener noreferrer">${linkLabel}</a>`;
|
|
749
830
|
}
|
|
750
831
|
}
|
|
@@ -758,6 +839,7 @@ class Banner {
|
|
|
758
839
|
class PreferenceCenter {
|
|
759
840
|
constructor(config, eventEmitter, currentConsent) {
|
|
760
841
|
this.element = null;
|
|
842
|
+
this.previousActiveElement = null;
|
|
761
843
|
this.config = config;
|
|
762
844
|
this.eventEmitter = eventEmitter;
|
|
763
845
|
this.currentConsent = currentConsent;
|
|
@@ -768,13 +850,13 @@ class PreferenceCenter {
|
|
|
768
850
|
show() {
|
|
769
851
|
const append = () => {
|
|
770
852
|
if (!this.element) {
|
|
853
|
+
this.previousActiveElement = document.activeElement;
|
|
771
854
|
this.element = this.createDOM();
|
|
772
855
|
document.body.appendChild(this.element);
|
|
773
856
|
this.attachListeners();
|
|
774
857
|
}
|
|
775
858
|
this.element.classList.add('is-visible');
|
|
776
859
|
this.trapFocus();
|
|
777
|
-
// Prevent body scroll
|
|
778
860
|
document.body.style.overflow = 'hidden';
|
|
779
861
|
};
|
|
780
862
|
if (!document.body) {
|
|
@@ -790,6 +872,11 @@ class PreferenceCenter {
|
|
|
790
872
|
var _a;
|
|
791
873
|
(_a = this.element) === null || _a === void 0 ? void 0 : _a.classList.remove('is-visible');
|
|
792
874
|
document.body.style.overflow = '';
|
|
875
|
+
// Restore focus to triggering element
|
|
876
|
+
if (this.previousActiveElement && document.contains(this.previousActiveElement)) {
|
|
877
|
+
this.previousActiveElement.focus();
|
|
878
|
+
this.previousActiveElement = null;
|
|
879
|
+
}
|
|
793
880
|
setTimeout(() => {
|
|
794
881
|
this.destroy();
|
|
795
882
|
}, 300);
|
|
@@ -824,7 +911,7 @@ class PreferenceCenter {
|
|
|
824
911
|
<polyline points="15 3 21 3 21 9"/>
|
|
825
912
|
<line x1="10" y1="14" x2="21" y2="3"/>
|
|
826
913
|
</svg>
|
|
827
|
-
${escapeHtml(translations.privacyPolicyLabel || '
|
|
914
|
+
${escapeHtml(translations.privacyPolicyLabel || 'Privacy Policy')}
|
|
828
915
|
</a>
|
|
829
916
|
`;
|
|
830
917
|
})()
|
|
@@ -842,7 +929,7 @@ class PreferenceCenter {
|
|
|
842
929
|
<div class="cc-modal__content">
|
|
843
930
|
<div class="cc-modal__header">
|
|
844
931
|
<h2 id="cc-modal-title">
|
|
845
|
-
${escapeHtml(translations.preferencesTitle || translations.title || '
|
|
932
|
+
${escapeHtml(translations.preferencesTitle || translations.title || 'Cookie Preferences')}
|
|
846
933
|
</h2>
|
|
847
934
|
</div>
|
|
848
935
|
|
|
@@ -859,13 +946,13 @@ class PreferenceCenter {
|
|
|
859
946
|
class="cc-btn cc-btn--secondary"
|
|
860
947
|
data-action="reject"
|
|
861
948
|
>
|
|
862
|
-
${escapeHtml(translations.essentialsOnly || '
|
|
949
|
+
${escapeHtml(translations.essentialsOnly || 'Essentials only')}
|
|
863
950
|
</button>
|
|
864
951
|
<button
|
|
865
952
|
class="cc-btn cc-btn--primary"
|
|
866
953
|
data-action="save"
|
|
867
954
|
>
|
|
868
|
-
${escapeHtml(translations.savePreferences || '
|
|
955
|
+
${escapeHtml(translations.savePreferences || 'Save preferences')}
|
|
869
956
|
</button>
|
|
870
957
|
</div>
|
|
871
958
|
</div>
|
|
@@ -883,7 +970,7 @@ class PreferenceCenter {
|
|
|
883
970
|
const categories = Object.entries(this.config.categories);
|
|
884
971
|
return categories
|
|
885
972
|
.map(([key, config]) => {
|
|
886
|
-
const checked = this.currentConsent[key];
|
|
973
|
+
const checked = this.currentConsent[key] === true;
|
|
887
974
|
const disabled = config.readOnly;
|
|
888
975
|
return `
|
|
889
976
|
<div class="cc-category">
|
|
@@ -930,13 +1017,16 @@ class PreferenceCenter {
|
|
|
930
1017
|
* Handle save preferences
|
|
931
1018
|
*/
|
|
932
1019
|
handleSave() {
|
|
933
|
-
var _a
|
|
1020
|
+
var _a;
|
|
934
1021
|
const checkboxes = (_a = this.element) === null || _a === void 0 ? void 0 : _a.querySelectorAll('input[data-category]');
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1022
|
+
// Initialize all configured categories to false
|
|
1023
|
+
const categories = { necessary: true, analytics: false, marketing: false };
|
|
1024
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
1025
|
+
if (key !== 'necessary') {
|
|
1026
|
+
categories[key] = false;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
// Override with actual checkbox values
|
|
940
1030
|
checkboxes === null || checkboxes === void 0 ? void 0 : checkboxes.forEach((checkbox) => {
|
|
941
1031
|
if (checkbox instanceof HTMLInputElement) {
|
|
942
1032
|
const category = checkbox.getAttribute('data-category');
|
|
@@ -946,22 +1036,16 @@ class PreferenceCenter {
|
|
|
946
1036
|
}
|
|
947
1037
|
});
|
|
948
1038
|
this.eventEmitter.emit('consent:update', categories);
|
|
949
|
-
(_c = (_b = this.config).onChange) === null || _c === void 0 ? void 0 : _c.call(_b, categories);
|
|
950
1039
|
this.hide();
|
|
951
1040
|
}
|
|
952
1041
|
/**
|
|
953
1042
|
* Handle reject all
|
|
954
1043
|
*/
|
|
955
1044
|
handleRejectAll() {
|
|
956
|
-
|
|
957
|
-
const
|
|
958
|
-
necessary
|
|
959
|
-
|
|
960
|
-
marketing: false,
|
|
961
|
-
};
|
|
962
|
-
// Only add preferences if it's configured
|
|
963
|
-
if ((_a = this.config.categories) === null || _a === void 0 ? void 0 : _a.preferences) {
|
|
964
|
-
necessaryOnly.preferences = false;
|
|
1045
|
+
const necessaryOnly = { necessary: true, analytics: false, marketing: false };
|
|
1046
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
1047
|
+
if (key !== 'necessary')
|
|
1048
|
+
necessaryOnly[key] = false;
|
|
965
1049
|
}
|
|
966
1050
|
this.eventEmitter.emit('consent:reject', necessaryOnly);
|
|
967
1051
|
this.hide();
|
|
@@ -976,9 +1060,7 @@ class PreferenceCenter {
|
|
|
976
1060
|
return;
|
|
977
1061
|
const firstFocusable = focusableElements[0];
|
|
978
1062
|
const lastFocusable = focusableElements[focusableElements.length - 1];
|
|
979
|
-
// Focus first element
|
|
980
1063
|
firstFocusable === null || firstFocusable === void 0 ? void 0 : firstFocusable.focus();
|
|
981
|
-
// Trap focus
|
|
982
1064
|
(_b = this.element) === null || _b === void 0 ? void 0 : _b.addEventListener('keydown', (e) => {
|
|
983
1065
|
if (e.key === 'Tab') {
|
|
984
1066
|
if (e.shiftKey && document.activeElement === firstFocusable) {
|
|
@@ -1066,7 +1148,7 @@ class FloatingWidget {
|
|
|
1066
1148
|
<div
|
|
1067
1149
|
class="cc-widget cc-widget--${escapeHtml(widgetPosition)} cc-widget--${escapeHtml(widgetStyle)}"
|
|
1068
1150
|
role="button"
|
|
1069
|
-
aria-label="${escapeHtml(translations.cookieSettings || '
|
|
1151
|
+
aria-label="${escapeHtml(translations.cookieSettings || 'Cookie settings')}"
|
|
1070
1152
|
tabindex="0"
|
|
1071
1153
|
data-theme="${escapeHtml(theme)}"
|
|
1072
1154
|
style="${colorStyle}"
|
|
@@ -1117,11 +1199,6 @@ class FloatingWidget {
|
|
|
1117
1199
|
|
|
1118
1200
|
/**
|
|
1119
1201
|
* GTMConsentMode - Full integration with Google Consent Mode v2
|
|
1120
|
-
*
|
|
1121
|
-
* Implements all required signals:
|
|
1122
|
-
* - ad_storage, ad_user_data, ad_personalization, analytics_storage (core GCM v2)
|
|
1123
|
-
* - functionality_storage, personalization_storage, security_storage (non-core)
|
|
1124
|
-
* - wait_for_update, url_passthrough, ads_data_redaction (advanced features)
|
|
1125
1202
|
*/
|
|
1126
1203
|
class GTMConsentMode {
|
|
1127
1204
|
constructor(dataLayerManager, config) {
|
|
@@ -1141,15 +1218,13 @@ class GTMConsentMode {
|
|
|
1141
1218
|
analytics_storage: 'denied',
|
|
1142
1219
|
functionality_storage: 'denied',
|
|
1143
1220
|
personalization_storage: 'denied',
|
|
1144
|
-
security_storage: 'granted',
|
|
1221
|
+
security_storage: 'granted',
|
|
1145
1222
|
};
|
|
1146
|
-
// Add wait_for_update to give CMP time to restore returning visitor consent
|
|
1147
1223
|
const waitForUpdate = (_a = this.config.gtmWaitForUpdate) !== null && _a !== void 0 ? _a : 500;
|
|
1148
1224
|
if (waitForUpdate > 0) {
|
|
1149
1225
|
defaults['wait_for_update'] = waitForUpdate;
|
|
1150
1226
|
}
|
|
1151
1227
|
this.dataLayerManager.pushConsent('default', defaults);
|
|
1152
|
-
// Set advanced features via gtag('set', ...)
|
|
1153
1228
|
if (this.config.gtmUrlPassthrough) {
|
|
1154
1229
|
this.dataLayerManager.pushSet('url_passthrough', true);
|
|
1155
1230
|
}
|
|
@@ -1159,7 +1234,6 @@ class GTMConsentMode {
|
|
|
1159
1234
|
}
|
|
1160
1235
|
/**
|
|
1161
1236
|
* Update consent state based on user choices
|
|
1162
|
-
* Called both on new consent and on page load for returning visitors
|
|
1163
1237
|
*/
|
|
1164
1238
|
updateConsent(categories) {
|
|
1165
1239
|
const gtmConsent = this.mapCategoriesToGTM(categories);
|
|
@@ -1169,14 +1243,19 @@ class GTMConsentMode {
|
|
|
1169
1243
|
* Map consent categories to GTM Consent Mode v2 format
|
|
1170
1244
|
*/
|
|
1171
1245
|
mapCategoriesToGTM(categories) {
|
|
1246
|
+
// When preferences category is not configured, default functionality to granted
|
|
1247
|
+
const hasPreferencesCategory = 'preferences' in this.config.categories;
|
|
1248
|
+
const preferencesGranted = hasPreferencesCategory
|
|
1249
|
+
? categories.preferences === true
|
|
1250
|
+
: true;
|
|
1172
1251
|
return {
|
|
1173
1252
|
ad_storage: categories.marketing ? 'granted' : 'denied',
|
|
1174
1253
|
ad_user_data: categories.marketing ? 'granted' : 'denied',
|
|
1175
1254
|
ad_personalization: categories.marketing ? 'granted' : 'denied',
|
|
1176
1255
|
analytics_storage: categories.analytics ? 'granted' : 'denied',
|
|
1177
|
-
functionality_storage:
|
|
1178
|
-
personalization_storage:
|
|
1179
|
-
security_storage: 'granted',
|
|
1256
|
+
functionality_storage: preferencesGranted ? 'granted' : 'denied',
|
|
1257
|
+
personalization_storage: preferencesGranted ? 'granted' : 'denied',
|
|
1258
|
+
security_storage: 'granted',
|
|
1180
1259
|
};
|
|
1181
1260
|
}
|
|
1182
1261
|
}
|
|
@@ -1312,6 +1391,111 @@ function clearDeniedCookies(categories) {
|
|
|
1312
1391
|
}
|
|
1313
1392
|
}
|
|
1314
1393
|
|
|
1394
|
+
/**
|
|
1395
|
+
* Built-in translations for supported languages
|
|
1396
|
+
* Users can override any string via config.translations
|
|
1397
|
+
*/
|
|
1398
|
+
const en = {
|
|
1399
|
+
title: 'We use cookies',
|
|
1400
|
+
description: 'We use cookies to improve your experience on our site. You can choose which cookies you accept.',
|
|
1401
|
+
acceptAll: 'Accept all',
|
|
1402
|
+
rejectAll: 'Essentials only',
|
|
1403
|
+
customize: 'Customize',
|
|
1404
|
+
savePreferences: 'Save preferences',
|
|
1405
|
+
essentialsOnly: 'Essentials only',
|
|
1406
|
+
preferencesTitle: 'Cookie Preferences',
|
|
1407
|
+
cookieSettings: 'Cookie settings',
|
|
1408
|
+
cookies: 'Cookies',
|
|
1409
|
+
privacyPolicyLabel: 'Privacy Policy',
|
|
1410
|
+
};
|
|
1411
|
+
const fr = {
|
|
1412
|
+
title: 'Nous utilisons des cookies',
|
|
1413
|
+
description: 'Ce site utilise des cookies pour améliorer votre expérience de navigation. Vous pouvez choisir les cookies que vous acceptez.',
|
|
1414
|
+
acceptAll: 'Tout accepter',
|
|
1415
|
+
rejectAll: 'Essentiels uniquement',
|
|
1416
|
+
customize: 'Personnaliser',
|
|
1417
|
+
savePreferences: 'Enregistrer',
|
|
1418
|
+
essentialsOnly: 'Essentiels uniquement',
|
|
1419
|
+
preferencesTitle: 'Préférences des cookies',
|
|
1420
|
+
cookieSettings: 'Paramètres des cookies',
|
|
1421
|
+
cookies: 'Cookies',
|
|
1422
|
+
privacyPolicyLabel: 'Politique de confidentialité',
|
|
1423
|
+
};
|
|
1424
|
+
const de = {
|
|
1425
|
+
title: 'Wir verwenden Cookies',
|
|
1426
|
+
description: 'Diese Website verwendet Cookies, um Ihr Erlebnis zu verbessern. Sie können wählen, welche Cookies Sie akzeptieren.',
|
|
1427
|
+
acceptAll: 'Alle akzeptieren',
|
|
1428
|
+
rejectAll: 'Nur essenzielle',
|
|
1429
|
+
customize: 'Anpassen',
|
|
1430
|
+
savePreferences: 'Speichern',
|
|
1431
|
+
essentialsOnly: 'Nur essenzielle',
|
|
1432
|
+
preferencesTitle: 'Cookie-Einstellungen',
|
|
1433
|
+
cookieSettings: 'Cookie-Einstellungen',
|
|
1434
|
+
cookies: 'Cookies',
|
|
1435
|
+
privacyPolicyLabel: 'Datenschutzrichtlinie',
|
|
1436
|
+
};
|
|
1437
|
+
const es = {
|
|
1438
|
+
title: 'Usamos cookies',
|
|
1439
|
+
description: 'Este sitio utiliza cookies para mejorar su experiencia. Puede elegir qué cookies acepta.',
|
|
1440
|
+
acceptAll: 'Aceptar todo',
|
|
1441
|
+
rejectAll: 'Solo esenciales',
|
|
1442
|
+
customize: 'Personalizar',
|
|
1443
|
+
savePreferences: 'Guardar',
|
|
1444
|
+
essentialsOnly: 'Solo esenciales',
|
|
1445
|
+
preferencesTitle: 'Preferencias de cookies',
|
|
1446
|
+
cookieSettings: 'Configuración de cookies',
|
|
1447
|
+
cookies: 'Cookies',
|
|
1448
|
+
privacyPolicyLabel: 'Política de privacidad',
|
|
1449
|
+
};
|
|
1450
|
+
const it = {
|
|
1451
|
+
title: 'Utilizziamo i cookie',
|
|
1452
|
+
description: 'Questo sito utilizza i cookie per migliorare la tua esperienza. Puoi scegliere quali cookie accettare.',
|
|
1453
|
+
acceptAll: 'Accetta tutti',
|
|
1454
|
+
rejectAll: 'Solo essenziali',
|
|
1455
|
+
customize: 'Personalizza',
|
|
1456
|
+
savePreferences: 'Salva',
|
|
1457
|
+
essentialsOnly: 'Solo essenziali',
|
|
1458
|
+
preferencesTitle: 'Preferenze cookie',
|
|
1459
|
+
cookieSettings: 'Impostazioni cookie',
|
|
1460
|
+
cookies: 'Cookie',
|
|
1461
|
+
privacyPolicyLabel: 'Informativa sulla privacy',
|
|
1462
|
+
};
|
|
1463
|
+
const nl = {
|
|
1464
|
+
title: 'Wij gebruiken cookies',
|
|
1465
|
+
description: 'Deze site maakt gebruik van cookies om uw ervaring te verbeteren. U kunt kiezen welke cookies u accepteert.',
|
|
1466
|
+
acceptAll: 'Alles accepteren',
|
|
1467
|
+
rejectAll: 'Alleen essentieel',
|
|
1468
|
+
customize: 'Aanpassen',
|
|
1469
|
+
savePreferences: 'Opslaan',
|
|
1470
|
+
essentialsOnly: 'Alleen essentieel',
|
|
1471
|
+
preferencesTitle: 'Cookie-voorkeuren',
|
|
1472
|
+
cookieSettings: 'Cookie-instellingen',
|
|
1473
|
+
cookies: 'Cookies',
|
|
1474
|
+
privacyPolicyLabel: 'Privacybeleid',
|
|
1475
|
+
};
|
|
1476
|
+
const pt = {
|
|
1477
|
+
title: 'Utilizamos cookies',
|
|
1478
|
+
description: 'Este site utiliza cookies para melhorar a sua experiência. Pode escolher quais cookies aceita.',
|
|
1479
|
+
acceptAll: 'Aceitar todos',
|
|
1480
|
+
rejectAll: 'Apenas essenciais',
|
|
1481
|
+
customize: 'Personalizar',
|
|
1482
|
+
savePreferences: 'Guardar',
|
|
1483
|
+
essentialsOnly: 'Apenas essenciais',
|
|
1484
|
+
preferencesTitle: 'Preferências de cookies',
|
|
1485
|
+
cookieSettings: 'Definições de cookies',
|
|
1486
|
+
cookies: 'Cookies',
|
|
1487
|
+
privacyPolicyLabel: 'Política de privacidade',
|
|
1488
|
+
};
|
|
1489
|
+
const builtInTranslations = {
|
|
1490
|
+
en,
|
|
1491
|
+
fr,
|
|
1492
|
+
de,
|
|
1493
|
+
es,
|
|
1494
|
+
it,
|
|
1495
|
+
nl,
|
|
1496
|
+
pt,
|
|
1497
|
+
};
|
|
1498
|
+
|
|
1315
1499
|
/**
|
|
1316
1500
|
* CookieConsent - Main orchestrator class
|
|
1317
1501
|
*/
|
|
@@ -1321,6 +1505,16 @@ class CookieConsent {
|
|
|
1321
1505
|
this.preferenceCenter = null;
|
|
1322
1506
|
this.floatingWidget = null;
|
|
1323
1507
|
this.gtmIntegration = null;
|
|
1508
|
+
this.hideTimeout = null;
|
|
1509
|
+
// SSR guard
|
|
1510
|
+
if (typeof window === 'undefined') {
|
|
1511
|
+
this.config = config;
|
|
1512
|
+
this.consentManager = null;
|
|
1513
|
+
this.storageManager = null;
|
|
1514
|
+
this.eventEmitter = null;
|
|
1515
|
+
this.scriptBlocker = null;
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1324
1518
|
this.config = this.validateConfig(config);
|
|
1325
1519
|
this.consentManager = new ConsentManager(this.config);
|
|
1326
1520
|
this.storageManager = new StorageManager();
|
|
@@ -1329,25 +1523,32 @@ class CookieConsent {
|
|
|
1329
1523
|
if (this.config.gtmConsentMode) {
|
|
1330
1524
|
this.gtmIntegration = new GTMConsentMode(new DataLayerManager(), this.config);
|
|
1331
1525
|
}
|
|
1332
|
-
// Listen for
|
|
1333
|
-
this.eventEmitter.on('preferences:show', () => {
|
|
1334
|
-
this.showPreferences();
|
|
1335
|
-
});
|
|
1336
|
-
// Listen for consent updates
|
|
1526
|
+
// Listen for consent events — callbacks are fired AFTER consent is persisted
|
|
1337
1527
|
this.eventEmitter.on('consent:accept', (categories) => {
|
|
1528
|
+
var _a, _b;
|
|
1338
1529
|
this.updateConsent(categories);
|
|
1530
|
+
(_b = (_a = this.config).onAccept) === null || _b === void 0 ? void 0 : _b.call(_a, categories);
|
|
1339
1531
|
});
|
|
1340
1532
|
this.eventEmitter.on('consent:reject', (categories) => {
|
|
1533
|
+
var _a, _b;
|
|
1341
1534
|
this.updateConsent(categories);
|
|
1535
|
+
(_b = (_a = this.config).onReject) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1342
1536
|
});
|
|
1343
1537
|
this.eventEmitter.on('consent:update', (categories) => {
|
|
1538
|
+
var _a, _b;
|
|
1344
1539
|
this.updateConsent(categories);
|
|
1540
|
+
(_b = (_a = this.config).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, categories);
|
|
1541
|
+
});
|
|
1542
|
+
this.eventEmitter.on('preferences:show', () => {
|
|
1543
|
+
this.showPreferences();
|
|
1345
1544
|
});
|
|
1346
1545
|
}
|
|
1347
1546
|
/**
|
|
1348
1547
|
* Initialize the cookie consent system
|
|
1349
1548
|
*/
|
|
1350
1549
|
init() {
|
|
1550
|
+
if (typeof window === 'undefined')
|
|
1551
|
+
return;
|
|
1351
1552
|
// 1. Start blocking scripts immediately
|
|
1352
1553
|
this.scriptBlocker.init();
|
|
1353
1554
|
// 2. Set GTM default consent BEFORE checking storage
|
|
@@ -1357,35 +1558,28 @@ class CookieConsent {
|
|
|
1357
1558
|
// 3. Check for existing consent
|
|
1358
1559
|
const storedConsent = this.storageManager.load();
|
|
1359
1560
|
if (storedConsent && !this.storageManager.isExpired(storedConsent)) {
|
|
1360
|
-
// Valid consent exists
|
|
1361
1561
|
if (this.consentManager.needsUpdate(storedConsent)) {
|
|
1362
|
-
// Policy updated, show banner again
|
|
1363
1562
|
if (this.config.autoShow) {
|
|
1364
1563
|
this.showBanner();
|
|
1365
1564
|
}
|
|
1366
1565
|
}
|
|
1367
1566
|
else {
|
|
1368
|
-
// Apply stored consent
|
|
1369
1567
|
this.applyConsent(storedConsent.categories);
|
|
1370
|
-
// Restore GTM consent for returning visitors (within wait_for_update window)
|
|
1371
1568
|
if (this.gtmIntegration) {
|
|
1372
1569
|
this.gtmIntegration.updateConsent(storedConsent.categories);
|
|
1373
1570
|
}
|
|
1374
1571
|
this.eventEmitter.emit('consent:load', storedConsent);
|
|
1375
|
-
// Show floating widget if enabled
|
|
1376
1572
|
if (this.config.showWidget) {
|
|
1377
1573
|
this.showFloatingWidget();
|
|
1378
1574
|
}
|
|
1379
1575
|
}
|
|
1380
1576
|
}
|
|
1381
1577
|
else {
|
|
1382
|
-
// No consent or expired
|
|
1383
1578
|
if (this.config.autoShow) {
|
|
1384
1579
|
this.showBanner();
|
|
1385
1580
|
}
|
|
1386
1581
|
}
|
|
1387
|
-
// Store instance globally
|
|
1388
|
-
// when used without a variable (e.g. new CookieConsent({}).init())
|
|
1582
|
+
// Store instance globally
|
|
1389
1583
|
window.cookieConsent = this;
|
|
1390
1584
|
this.eventEmitter.emit('consent:init');
|
|
1391
1585
|
}
|
|
@@ -1408,14 +1602,18 @@ class CookieConsent {
|
|
|
1408
1602
|
showPreferences() {
|
|
1409
1603
|
var _a;
|
|
1410
1604
|
const stored = (_a = this.storageManager.load()) === null || _a === void 0 ? void 0 : _a.categories;
|
|
1411
|
-
// Default to all ON when no prior consent
|
|
1605
|
+
// Default to all ON when no prior consent
|
|
1412
1606
|
const currentConsent = stored || {
|
|
1413
1607
|
necessary: true,
|
|
1414
1608
|
analytics: true,
|
|
1415
1609
|
marketing: true,
|
|
1416
|
-
preferences: true,
|
|
1417
1610
|
};
|
|
1418
|
-
//
|
|
1611
|
+
// Add any configured categories not in current consent
|
|
1612
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
1613
|
+
if (!(key in currentConsent)) {
|
|
1614
|
+
currentConsent[key] = key === 'necessary';
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1419
1617
|
if (this.preferenceCenter) {
|
|
1420
1618
|
this.preferenceCenter.destroy();
|
|
1421
1619
|
}
|
|
@@ -1432,11 +1630,11 @@ class CookieConsent {
|
|
|
1432
1630
|
if (this.gtmIntegration) {
|
|
1433
1631
|
this.gtmIntegration.updateConsent(categories);
|
|
1434
1632
|
}
|
|
1435
|
-
// Show floating widget after consent is given
|
|
1633
|
+
// Show floating widget after consent is given
|
|
1436
1634
|
if (this.config.showWidget) {
|
|
1437
1635
|
setTimeout(() => {
|
|
1438
1636
|
this.showFloatingWidget();
|
|
1439
|
-
}, 400);
|
|
1637
|
+
}, 400);
|
|
1440
1638
|
}
|
|
1441
1639
|
}
|
|
1442
1640
|
/**
|
|
@@ -1449,14 +1647,19 @@ class CookieConsent {
|
|
|
1449
1647
|
* Reset consent (clear stored data and show banner)
|
|
1450
1648
|
*/
|
|
1451
1649
|
reset() {
|
|
1650
|
+
var _a, _b;
|
|
1452
1651
|
this.storageManager.clear();
|
|
1453
1652
|
this.scriptBlocker.block();
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1653
|
+
const denied = { necessary: true, analytics: false, marketing: false };
|
|
1654
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
1655
|
+
if (key !== 'necessary')
|
|
1656
|
+
denied[key] = false;
|
|
1657
|
+
}
|
|
1658
|
+
clearDeniedCookies(denied);
|
|
1457
1659
|
if (this.gtmIntegration) {
|
|
1458
|
-
this.gtmIntegration.updateConsent(
|
|
1660
|
+
this.gtmIntegration.updateConsent(denied);
|
|
1459
1661
|
}
|
|
1662
|
+
(_b = (_a = this.config).onReject) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1460
1663
|
this.showBanner();
|
|
1461
1664
|
}
|
|
1462
1665
|
/**
|
|
@@ -1476,6 +1679,10 @@ class CookieConsent {
|
|
|
1476
1679
|
*/
|
|
1477
1680
|
destroy() {
|
|
1478
1681
|
var _a, _b, _c, _d;
|
|
1682
|
+
if (this.hideTimeout) {
|
|
1683
|
+
clearTimeout(this.hideTimeout);
|
|
1684
|
+
this.hideTimeout = null;
|
|
1685
|
+
}
|
|
1479
1686
|
(_a = this.banner) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
1480
1687
|
this.banner = null;
|
|
1481
1688
|
(_b = this.preferenceCenter) === null || _b === void 0 ? void 0 : _b.destroy();
|
|
@@ -1483,6 +1690,10 @@ class CookieConsent {
|
|
|
1483
1690
|
(_c = this.floatingWidget) === null || _c === void 0 ? void 0 : _c.destroy();
|
|
1484
1691
|
this.floatingWidget = null;
|
|
1485
1692
|
(_d = this.scriptBlocker) === null || _d === void 0 ? void 0 : _d.destroy();
|
|
1693
|
+
this.eventEmitter.clear();
|
|
1694
|
+
if (window.cookieConsent === this) {
|
|
1695
|
+
window.cookieConsent = undefined;
|
|
1696
|
+
}
|
|
1486
1697
|
}
|
|
1487
1698
|
/**
|
|
1488
1699
|
* Show the banner
|
|
@@ -1508,33 +1719,37 @@ class CookieConsent {
|
|
|
1508
1719
|
*/
|
|
1509
1720
|
applyConsent(categories) {
|
|
1510
1721
|
this.scriptBlocker.unblock(categories);
|
|
1511
|
-
// CNIL/GDPR: actively delete cookies for denied categories
|
|
1512
1722
|
clearDeniedCookies(categories);
|
|
1513
1723
|
}
|
|
1514
1724
|
/**
|
|
1515
1725
|
* Validate and set default config values
|
|
1516
1726
|
*/
|
|
1517
1727
|
validateConfig(config) {
|
|
1518
|
-
|
|
1728
|
+
var _a;
|
|
1729
|
+
// Merge built-in language translations with user overrides
|
|
1730
|
+
const langKey = ((_a = config.language) === null || _a === void 0 ? void 0 : _a.toLowerCase().split('-')[0]) || 'en';
|
|
1731
|
+
const langDefaults = builtInTranslations[langKey] || builtInTranslations['en'];
|
|
1732
|
+
const mergedTranslations = Object.assign(Object.assign({}, langDefaults), config.translations);
|
|
1733
|
+
return Object.assign(Object.assign({}, config), { translations: mergedTranslations, categories: config.categories || {
|
|
1519
1734
|
necessary: {
|
|
1520
1735
|
enabled: true,
|
|
1521
1736
|
readOnly: true,
|
|
1522
|
-
label: '
|
|
1523
|
-
description: '
|
|
1737
|
+
label: 'Essential',
|
|
1738
|
+
description: 'Required for the website to function properly.',
|
|
1524
1739
|
},
|
|
1525
1740
|
analytics: {
|
|
1526
1741
|
enabled: true,
|
|
1527
1742
|
readOnly: false,
|
|
1528
|
-
label: '
|
|
1529
|
-
description: '
|
|
1743
|
+
label: 'Analytics',
|
|
1744
|
+
description: 'Help us understand how you use our site.',
|
|
1530
1745
|
},
|
|
1531
1746
|
marketing: {
|
|
1532
1747
|
enabled: true,
|
|
1533
1748
|
readOnly: false,
|
|
1534
1749
|
label: 'Marketing',
|
|
1535
|
-
description: '
|
|
1750
|
+
description: 'Used to deliver relevant advertisements.',
|
|
1536
1751
|
},
|
|
1537
|
-
}, mode: config.mode || 'opt-in', autoShow: config.autoShow !== undefined ? config.autoShow : true, revision: config.revision || 1, gtmConsentMode: config.gtmConsentMode
|
|
1752
|
+
}, mode: config.mode || 'opt-in', autoShow: config.autoShow !== undefined ? config.autoShow : true, revision: config.revision || 1, gtmConsentMode: config.gtmConsentMode || false, disablePageInteraction: config.disablePageInteraction || false, theme: config.theme || 'light', position: config.position || 'bottom-left', layout: config.layout || 'box', backdropBlur: config.backdropBlur !== false, animationStyle: config.animationStyle || 'smooth', preferencesPosition: config.preferencesPosition || 'center', showWidget: config.showWidget !== undefined ? config.showWidget : true, widgetPosition: config.widgetPosition || 'bottom-left', widgetStyle: config.widgetStyle || 'compact' });
|
|
1538
1753
|
}
|
|
1539
1754
|
}
|
|
1540
1755
|
|