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.js
CHANGED
|
@@ -49,13 +49,20 @@
|
|
|
49
49
|
* Clear consent record from localStorage
|
|
50
50
|
*/
|
|
51
51
|
clear() {
|
|
52
|
-
|
|
52
|
+
try {
|
|
53
|
+
localStorage.removeItem(StorageManager.STORAGE_KEY);
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
console.error('Failed to clear consent:', e);
|
|
57
|
+
}
|
|
53
58
|
}
|
|
54
59
|
/**
|
|
55
60
|
* Check if consent record has expired
|
|
56
61
|
*/
|
|
57
62
|
isExpired(consent) {
|
|
58
63
|
const expiry = new Date(consent.expiresAt);
|
|
64
|
+
if (isNaN(expiry.getTime()))
|
|
65
|
+
return true;
|
|
59
66
|
return expiry < new Date();
|
|
60
67
|
}
|
|
61
68
|
/**
|
|
@@ -66,7 +73,6 @@
|
|
|
66
73
|
typeof data.version === 'number' &&
|
|
67
74
|
typeof data.timestamp === 'string' &&
|
|
68
75
|
typeof data.categories === 'object' &&
|
|
69
|
-
typeof data.userAgent === 'string' &&
|
|
70
76
|
typeof data.expiresAt === 'string');
|
|
71
77
|
}
|
|
72
78
|
/**
|
|
@@ -82,11 +88,20 @@
|
|
|
82
88
|
const now = new Date();
|
|
83
89
|
const expiryDate = new Date(now);
|
|
84
90
|
expiryDate.setMonth(expiryDate.getMonth() + StorageManager.EXPIRY_MONTHS);
|
|
91
|
+
// Coerce category values to booleans
|
|
92
|
+
const rawCategories = record.categories;
|
|
93
|
+
const categories = {
|
|
94
|
+
necessary: rawCategories.necessary === true,
|
|
95
|
+
analytics: rawCategories.analytics === true,
|
|
96
|
+
marketing: rawCategories.marketing === true,
|
|
97
|
+
};
|
|
98
|
+
if ('preferences' in rawCategories) {
|
|
99
|
+
categories.preferences = rawCategories.preferences === true;
|
|
100
|
+
}
|
|
85
101
|
return {
|
|
86
102
|
version: typeof record.version === 'number' ? record.version : 1,
|
|
87
103
|
timestamp: typeof record.timestamp === 'string' ? record.timestamp : now.toISOString(),
|
|
88
|
-
categories
|
|
89
|
-
userAgent: typeof record.userAgent === 'string' ? record.userAgent : navigator.userAgent,
|
|
104
|
+
categories,
|
|
90
105
|
expiresAt: typeof record.expiresAt === 'string' ? record.expiresAt : expiryDate.toISOString(),
|
|
91
106
|
};
|
|
92
107
|
}
|
|
@@ -101,6 +116,7 @@
|
|
|
101
116
|
*/
|
|
102
117
|
class ConsentManager {
|
|
103
118
|
constructor(config) {
|
|
119
|
+
this.consent = null;
|
|
104
120
|
this.config = config;
|
|
105
121
|
}
|
|
106
122
|
/**
|
|
@@ -119,6 +135,10 @@
|
|
|
119
135
|
}
|
|
120
136
|
}
|
|
121
137
|
}
|
|
138
|
+
// Coerce all values to booleans
|
|
139
|
+
for (const key of Object.keys(categories)) {
|
|
140
|
+
categories[key] = categories[key] === true;
|
|
141
|
+
}
|
|
122
142
|
return true;
|
|
123
143
|
}
|
|
124
144
|
/**
|
|
@@ -135,13 +155,12 @@
|
|
|
135
155
|
* Check if user needs to give consent
|
|
136
156
|
*/
|
|
137
157
|
needsConsent() {
|
|
138
|
-
return this.consent ===
|
|
158
|
+
return this.consent === null;
|
|
139
159
|
}
|
|
140
160
|
/**
|
|
141
161
|
* Check if stored consent needs update due to policy change
|
|
142
162
|
*/
|
|
143
163
|
needsUpdate(storedConsent) {
|
|
144
|
-
// Check if policy version has changed
|
|
145
164
|
return storedConsent.version < this.config.revision;
|
|
146
165
|
}
|
|
147
166
|
/**
|
|
@@ -161,7 +180,6 @@
|
|
|
161
180
|
version: this.config.revision,
|
|
162
181
|
timestamp: now.toISOString(),
|
|
163
182
|
categories: Object.assign({}, categories),
|
|
164
|
-
userAgent: navigator.userAgent,
|
|
165
183
|
expiresAt: expiryDate.toISOString(),
|
|
166
184
|
};
|
|
167
185
|
}
|
|
@@ -402,7 +420,6 @@
|
|
|
402
420
|
class CategoryManager {
|
|
403
421
|
constructor() {
|
|
404
422
|
this.categories = new Map();
|
|
405
|
-
// Initialize with common patterns
|
|
406
423
|
this.initializeDefaultPatterns();
|
|
407
424
|
}
|
|
408
425
|
/**
|
|
@@ -439,11 +456,11 @@
|
|
|
439
456
|
}
|
|
440
457
|
/**
|
|
441
458
|
* Initialize default URL patterns for common tracking services
|
|
459
|
+
* Note: GTM is NOT auto-categorized — it should be managed via GTM Consent Mode v2
|
|
442
460
|
*/
|
|
443
461
|
initializeDefaultPatterns() {
|
|
444
462
|
this.categories.set('analytics', [
|
|
445
463
|
'google-analytics.com',
|
|
446
|
-
'googletagmanager.com',
|
|
447
464
|
'analytics.google.com',
|
|
448
465
|
'plausible.io',
|
|
449
466
|
'matomo.org',
|
|
@@ -453,6 +470,7 @@
|
|
|
453
470
|
'amplitude.com',
|
|
454
471
|
]);
|
|
455
472
|
this.categories.set('marketing', [
|
|
473
|
+
'googletagmanager.com',
|
|
456
474
|
'facebook.net',
|
|
457
475
|
'facebook.com/tr',
|
|
458
476
|
'connect.facebook.net',
|
|
@@ -464,6 +482,7 @@
|
|
|
464
482
|
'adroll.com',
|
|
465
483
|
'taboola.com',
|
|
466
484
|
'outbrain.com',
|
|
485
|
+
'tiktok.com',
|
|
467
486
|
]);
|
|
468
487
|
this.categories.set('necessary', []);
|
|
469
488
|
}
|
|
@@ -514,18 +533,47 @@
|
|
|
514
533
|
// Allow hsl/hsla
|
|
515
534
|
if (/^hsla?\(\s*[\d\s,./%deg]+\)$/.test(trimmed))
|
|
516
535
|
return trimmed;
|
|
517
|
-
// Allow CSS named colors (basic set)
|
|
518
|
-
|
|
536
|
+
// Allow CSS named colors (basic set) but block CSS keywords that could be abused
|
|
537
|
+
const CSS_KEYWORDS = ['inherit', 'initial', 'unset', 'revert', 'revert-layer'];
|
|
538
|
+
if (/^[a-zA-Z]+$/.test(trimmed) && !CSS_KEYWORDS.includes(trimmed.toLowerCase())) {
|
|
519
539
|
return trimmed;
|
|
540
|
+
}
|
|
520
541
|
return '';
|
|
521
542
|
}
|
|
522
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Normalize any supported color format to 6-digit hex
|
|
546
|
+
* Supports: #RGB, #RRGGBB, #RRGGBBAA, named colors
|
|
547
|
+
* Returns null if conversion fails
|
|
548
|
+
*/
|
|
549
|
+
function normalizeToHex6(color) {
|
|
550
|
+
const trimmed = color.trim();
|
|
551
|
+
// Already 6-digit hex
|
|
552
|
+
if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) {
|
|
553
|
+
return trimmed;
|
|
554
|
+
}
|
|
555
|
+
// 3-digit hex → expand to 6-digit
|
|
556
|
+
if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {
|
|
557
|
+
const r = trimmed[1];
|
|
558
|
+
const g = trimmed[2];
|
|
559
|
+
const b = trimmed[3];
|
|
560
|
+
return `#${r}${r}${g}${g}${b}${b}`;
|
|
561
|
+
}
|
|
562
|
+
// 8-digit hex (with alpha) → strip alpha
|
|
563
|
+
if (/^#[0-9a-fA-F]{8}$/.test(trimmed)) {
|
|
564
|
+
return trimmed.substring(0, 7);
|
|
565
|
+
}
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
523
568
|
/**
|
|
524
569
|
* Adjust a hex color brightness by a percentage
|
|
525
570
|
* Negative = darker, positive = lighter
|
|
526
571
|
*/
|
|
527
572
|
function adjustColorBrightness(color, percent) {
|
|
528
|
-
const
|
|
573
|
+
const hex6 = normalizeToHex6(color);
|
|
574
|
+
if (!hex6)
|
|
575
|
+
return color;
|
|
576
|
+
const hex = hex6.replace('#', '');
|
|
529
577
|
const r = parseInt(hex.substring(0, 2), 16);
|
|
530
578
|
const g = parseInt(hex.substring(2, 4), 16);
|
|
531
579
|
const b = parseInt(hex.substring(4, 6), 16);
|
|
@@ -545,8 +593,13 @@
|
|
|
545
593
|
function buildColorStyle(safeColor) {
|
|
546
594
|
if (!safeColor)
|
|
547
595
|
return '';
|
|
548
|
-
|
|
549
|
-
|
|
596
|
+
// Only generate hover color for hex colors
|
|
597
|
+
const hex6 = normalizeToHex6(safeColor);
|
|
598
|
+
if (!hex6) {
|
|
599
|
+
return `--cc-primary: ${safeColor};`;
|
|
600
|
+
}
|
|
601
|
+
const hover = adjustColorBrightness(hex6, -15);
|
|
602
|
+
return `--cc-primary: ${hex6}; --cc-primary-hover: ${hover};`;
|
|
550
603
|
}
|
|
551
604
|
|
|
552
605
|
/**
|
|
@@ -555,6 +608,8 @@
|
|
|
555
608
|
class Banner {
|
|
556
609
|
constructor(config, eventEmitter) {
|
|
557
610
|
this.element = null;
|
|
611
|
+
this.hideTimeout = null;
|
|
612
|
+
this.previousActiveElement = null;
|
|
558
613
|
this.config = config;
|
|
559
614
|
this.eventEmitter = eventEmitter;
|
|
560
615
|
}
|
|
@@ -562,8 +617,14 @@
|
|
|
562
617
|
* Show the banner
|
|
563
618
|
*/
|
|
564
619
|
show() {
|
|
620
|
+
// Clear any pending hide timeout
|
|
621
|
+
if (this.hideTimeout) {
|
|
622
|
+
clearTimeout(this.hideTimeout);
|
|
623
|
+
this.hideTimeout = null;
|
|
624
|
+
}
|
|
565
625
|
const append = () => {
|
|
566
626
|
if (!this.element) {
|
|
627
|
+
this.previousActiveElement = document.activeElement;
|
|
567
628
|
this.element = this.createDOM();
|
|
568
629
|
document.body.appendChild(this.element);
|
|
569
630
|
this.attachListeners();
|
|
@@ -576,9 +637,9 @@
|
|
|
576
637
|
// Disable page interaction if configured
|
|
577
638
|
if (this.config.disablePageInteraction) {
|
|
578
639
|
document.body.style.overflow = 'hidden';
|
|
640
|
+
this.trapFocus();
|
|
579
641
|
}
|
|
580
642
|
};
|
|
581
|
-
// Wait for body if not yet available
|
|
582
643
|
if (!document.body) {
|
|
583
644
|
document.addEventListener('DOMContentLoaded', append);
|
|
584
645
|
return;
|
|
@@ -591,22 +652,30 @@
|
|
|
591
652
|
hide() {
|
|
592
653
|
var _a;
|
|
593
654
|
(_a = this.element) === null || _a === void 0 ? void 0 : _a.classList.remove('is-visible');
|
|
594
|
-
// Re-enable page interaction
|
|
595
655
|
if (this.config.disablePageInteraction) {
|
|
596
656
|
document.body.style.overflow = '';
|
|
597
657
|
}
|
|
598
|
-
setTimeout(() => {
|
|
658
|
+
this.hideTimeout = setTimeout(() => {
|
|
599
659
|
this.destroy();
|
|
600
|
-
}, 300);
|
|
660
|
+
}, 300);
|
|
601
661
|
}
|
|
602
662
|
/**
|
|
603
663
|
* Destroy the banner
|
|
604
664
|
*/
|
|
605
665
|
destroy() {
|
|
666
|
+
if (this.hideTimeout) {
|
|
667
|
+
clearTimeout(this.hideTimeout);
|
|
668
|
+
this.hideTimeout = null;
|
|
669
|
+
}
|
|
606
670
|
if (this.element) {
|
|
607
671
|
this.element.remove();
|
|
608
672
|
this.element = null;
|
|
609
673
|
}
|
|
674
|
+
// Restore focus
|
|
675
|
+
if (this.previousActiveElement && document.contains(this.previousActiveElement)) {
|
|
676
|
+
this.previousActiveElement.focus();
|
|
677
|
+
this.previousActiveElement = null;
|
|
678
|
+
}
|
|
610
679
|
}
|
|
611
680
|
/**
|
|
612
681
|
* Create DOM structure for banner
|
|
@@ -617,12 +686,14 @@
|
|
|
617
686
|
const position = this.config.position || 'bottom';
|
|
618
687
|
const layout = this.config.layout || 'bar';
|
|
619
688
|
const backdropBlur = this.config.backdropBlur !== false;
|
|
689
|
+
const isModal = this.config.disablePageInteraction;
|
|
620
690
|
const safeColor = this.config.primaryColor ? sanitizeColor(this.config.primaryColor) : '';
|
|
621
691
|
const colorStyle = buildColorStyle(safeColor);
|
|
622
692
|
const template = `
|
|
623
693
|
<div
|
|
624
694
|
class="cc-banner cc-banner--${escapeHtml(position)} cc-banner--${escapeHtml(layout)} ${backdropBlur ? 'cc-backdrop-blur' : ''}"
|
|
625
|
-
role="region"
|
|
695
|
+
role="${isModal ? 'dialog' : 'region'}"
|
|
696
|
+
${isModal ? 'aria-modal="true"' : ''}
|
|
626
697
|
aria-label="Cookie consent"
|
|
627
698
|
aria-live="polite"
|
|
628
699
|
data-theme="${escapeHtml(theme)}"
|
|
@@ -631,7 +702,7 @@
|
|
|
631
702
|
<div class="cc-banner__container">
|
|
632
703
|
<div class="cc-banner__content">
|
|
633
704
|
<h2 class="cc-banner__title">
|
|
634
|
-
${escapeHtml(translations.title || '
|
|
705
|
+
${escapeHtml(translations.title || 'We use cookies')}
|
|
635
706
|
</h2>
|
|
636
707
|
<p class="cc-banner__description">
|
|
637
708
|
${this.getDescriptionHTML()}
|
|
@@ -641,23 +712,23 @@
|
|
|
641
712
|
<button
|
|
642
713
|
class="cc-btn cc-btn--ghost"
|
|
643
714
|
data-action="reject"
|
|
644
|
-
aria-label="${escapeHtml(translations.rejectAll || '
|
|
715
|
+
aria-label="${escapeHtml(translations.rejectAll || 'Essentials only')}"
|
|
645
716
|
>
|
|
646
|
-
${escapeHtml(translations.rejectAll || '
|
|
717
|
+
${escapeHtml(translations.rejectAll || 'Essentials only')}
|
|
647
718
|
</button>
|
|
648
719
|
<button
|
|
649
720
|
class="cc-btn cc-btn--tertiary"
|
|
650
721
|
data-action="customize"
|
|
651
|
-
aria-label="${escapeHtml(translations.customize || '
|
|
722
|
+
aria-label="${escapeHtml(translations.customize || 'Customize')}"
|
|
652
723
|
>
|
|
653
|
-
${escapeHtml(translations.customize || '
|
|
724
|
+
${escapeHtml(translations.customize || 'Customize')}
|
|
654
725
|
</button>
|
|
655
726
|
<button
|
|
656
727
|
class="cc-btn cc-btn--accept"
|
|
657
728
|
data-action="accept"
|
|
658
|
-
aria-label="${escapeHtml(translations.acceptAll || '
|
|
729
|
+
aria-label="${escapeHtml(translations.acceptAll || 'Accept all')}"
|
|
659
730
|
>
|
|
660
|
-
${escapeHtml(translations.acceptAll || '
|
|
731
|
+
${escapeHtml(translations.acceptAll || 'Accept all')}
|
|
661
732
|
</button>
|
|
662
733
|
</div>
|
|
663
734
|
</div>
|
|
@@ -689,10 +760,8 @@
|
|
|
689
760
|
break;
|
|
690
761
|
}
|
|
691
762
|
});
|
|
692
|
-
// Keyboard support
|
|
693
763
|
(_b = this.element) === null || _b === void 0 ? void 0 : _b.addEventListener('keydown', (e) => {
|
|
694
764
|
if (e.key === 'Escape' && this.config.disablePageInteraction) {
|
|
695
|
-
// Allow ESC to close if page interaction is disabled
|
|
696
765
|
this.handleRejectAll();
|
|
697
766
|
}
|
|
698
767
|
});
|
|
@@ -701,36 +770,24 @@
|
|
|
701
770
|
* Handle accept all action
|
|
702
771
|
*/
|
|
703
772
|
handleAcceptAll() {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
marketing: true,
|
|
709
|
-
};
|
|
710
|
-
// Only add preferences if it's configured
|
|
711
|
-
if ((_a = this.config.categories) === null || _a === void 0 ? void 0 : _a.preferences) {
|
|
712
|
-
allCategories.preferences = true;
|
|
773
|
+
const allCategories = { necessary: true, analytics: true, marketing: true };
|
|
774
|
+
// Add all configured categories
|
|
775
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
776
|
+
allCategories[key] = true;
|
|
713
777
|
}
|
|
714
778
|
this.eventEmitter.emit('consent:accept', allCategories);
|
|
715
|
-
(_c = (_b = this.config).onAccept) === null || _c === void 0 ? void 0 : _c.call(_b, allCategories);
|
|
716
779
|
this.hide();
|
|
717
780
|
}
|
|
718
781
|
/**
|
|
719
782
|
* Handle reject all action
|
|
720
783
|
*/
|
|
721
784
|
handleRejectAll() {
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
necessary
|
|
725
|
-
|
|
726
|
-
marketing: false,
|
|
727
|
-
};
|
|
728
|
-
// Only add preferences if it's configured
|
|
729
|
-
if ((_a = this.config.categories) === null || _a === void 0 ? void 0 : _a.preferences) {
|
|
730
|
-
necessaryOnly.preferences = false;
|
|
785
|
+
const necessaryOnly = { necessary: true, analytics: false, marketing: false };
|
|
786
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
787
|
+
if (key !== 'necessary')
|
|
788
|
+
necessaryOnly[key] = false;
|
|
731
789
|
}
|
|
732
790
|
this.eventEmitter.emit('consent:reject', necessaryOnly);
|
|
733
|
-
(_c = (_b = this.config).onReject) === null || _c === void 0 ? void 0 : _c.call(_b);
|
|
734
791
|
this.hide();
|
|
735
792
|
}
|
|
736
793
|
/**
|
|
@@ -740,17 +797,41 @@
|
|
|
740
797
|
this.eventEmitter.emit('preferences:show');
|
|
741
798
|
this.hide();
|
|
742
799
|
}
|
|
800
|
+
/**
|
|
801
|
+
* Trap focus within banner (when disablePageInteraction is true)
|
|
802
|
+
*/
|
|
803
|
+
trapFocus() {
|
|
804
|
+
var _a, _b;
|
|
805
|
+
const focusableElements = (_a = this.element) === null || _a === void 0 ? void 0 : _a.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
806
|
+
if (!focusableElements || focusableElements.length === 0)
|
|
807
|
+
return;
|
|
808
|
+
const firstFocusable = focusableElements[0];
|
|
809
|
+
const lastFocusable = focusableElements[focusableElements.length - 1];
|
|
810
|
+
firstFocusable === null || firstFocusable === void 0 ? void 0 : firstFocusable.focus();
|
|
811
|
+
(_b = this.element) === null || _b === void 0 ? void 0 : _b.addEventListener('keydown', (e) => {
|
|
812
|
+
if (e.key === 'Tab') {
|
|
813
|
+
if (e.shiftKey && document.activeElement === firstFocusable) {
|
|
814
|
+
e.preventDefault();
|
|
815
|
+
lastFocusable.focus();
|
|
816
|
+
}
|
|
817
|
+
else if (!e.shiftKey && document.activeElement === lastFocusable) {
|
|
818
|
+
e.preventDefault();
|
|
819
|
+
firstFocusable.focus();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
}
|
|
743
824
|
/**
|
|
744
825
|
* Generate description HTML with privacy policy link
|
|
745
826
|
*/
|
|
746
827
|
getDescriptionHTML() {
|
|
747
828
|
const translations = this.config.translations || {};
|
|
748
|
-
const defaultDescription = '
|
|
829
|
+
const defaultDescription = 'We use cookies to improve your experience on our site. You can choose which cookies you accept.';
|
|
749
830
|
const description = escapeHtml(translations.description || defaultDescription);
|
|
750
831
|
if (translations.privacyPolicyUrl) {
|
|
751
832
|
const safeUrl = sanitizeUrl(translations.privacyPolicyUrl);
|
|
752
833
|
if (safeUrl) {
|
|
753
|
-
const linkLabel = escapeHtml(translations.privacyPolicyLabel || '
|
|
834
|
+
const linkLabel = escapeHtml(translations.privacyPolicyLabel || 'Privacy Policy');
|
|
754
835
|
return `${description} <a href="${safeUrl}" target="_blank" rel="noopener noreferrer">${linkLabel}</a>`;
|
|
755
836
|
}
|
|
756
837
|
}
|
|
@@ -764,6 +845,7 @@
|
|
|
764
845
|
class PreferenceCenter {
|
|
765
846
|
constructor(config, eventEmitter, currentConsent) {
|
|
766
847
|
this.element = null;
|
|
848
|
+
this.previousActiveElement = null;
|
|
767
849
|
this.config = config;
|
|
768
850
|
this.eventEmitter = eventEmitter;
|
|
769
851
|
this.currentConsent = currentConsent;
|
|
@@ -774,13 +856,13 @@
|
|
|
774
856
|
show() {
|
|
775
857
|
const append = () => {
|
|
776
858
|
if (!this.element) {
|
|
859
|
+
this.previousActiveElement = document.activeElement;
|
|
777
860
|
this.element = this.createDOM();
|
|
778
861
|
document.body.appendChild(this.element);
|
|
779
862
|
this.attachListeners();
|
|
780
863
|
}
|
|
781
864
|
this.element.classList.add('is-visible');
|
|
782
865
|
this.trapFocus();
|
|
783
|
-
// Prevent body scroll
|
|
784
866
|
document.body.style.overflow = 'hidden';
|
|
785
867
|
};
|
|
786
868
|
if (!document.body) {
|
|
@@ -796,6 +878,11 @@
|
|
|
796
878
|
var _a;
|
|
797
879
|
(_a = this.element) === null || _a === void 0 ? void 0 : _a.classList.remove('is-visible');
|
|
798
880
|
document.body.style.overflow = '';
|
|
881
|
+
// Restore focus to triggering element
|
|
882
|
+
if (this.previousActiveElement && document.contains(this.previousActiveElement)) {
|
|
883
|
+
this.previousActiveElement.focus();
|
|
884
|
+
this.previousActiveElement = null;
|
|
885
|
+
}
|
|
799
886
|
setTimeout(() => {
|
|
800
887
|
this.destroy();
|
|
801
888
|
}, 300);
|
|
@@ -830,7 +917,7 @@
|
|
|
830
917
|
<polyline points="15 3 21 3 21 9"/>
|
|
831
918
|
<line x1="10" y1="14" x2="21" y2="3"/>
|
|
832
919
|
</svg>
|
|
833
|
-
${escapeHtml(translations.privacyPolicyLabel || '
|
|
920
|
+
${escapeHtml(translations.privacyPolicyLabel || 'Privacy Policy')}
|
|
834
921
|
</a>
|
|
835
922
|
`;
|
|
836
923
|
})()
|
|
@@ -848,7 +935,7 @@
|
|
|
848
935
|
<div class="cc-modal__content">
|
|
849
936
|
<div class="cc-modal__header">
|
|
850
937
|
<h2 id="cc-modal-title">
|
|
851
|
-
${escapeHtml(translations.preferencesTitle || translations.title || '
|
|
938
|
+
${escapeHtml(translations.preferencesTitle || translations.title || 'Cookie Preferences')}
|
|
852
939
|
</h2>
|
|
853
940
|
</div>
|
|
854
941
|
|
|
@@ -865,13 +952,13 @@
|
|
|
865
952
|
class="cc-btn cc-btn--secondary"
|
|
866
953
|
data-action="reject"
|
|
867
954
|
>
|
|
868
|
-
${escapeHtml(translations.essentialsOnly || '
|
|
955
|
+
${escapeHtml(translations.essentialsOnly || 'Essentials only')}
|
|
869
956
|
</button>
|
|
870
957
|
<button
|
|
871
958
|
class="cc-btn cc-btn--primary"
|
|
872
959
|
data-action="save"
|
|
873
960
|
>
|
|
874
|
-
${escapeHtml(translations.savePreferences || '
|
|
961
|
+
${escapeHtml(translations.savePreferences || 'Save preferences')}
|
|
875
962
|
</button>
|
|
876
963
|
</div>
|
|
877
964
|
</div>
|
|
@@ -889,7 +976,7 @@
|
|
|
889
976
|
const categories = Object.entries(this.config.categories);
|
|
890
977
|
return categories
|
|
891
978
|
.map(([key, config]) => {
|
|
892
|
-
const checked = this.currentConsent[key];
|
|
979
|
+
const checked = this.currentConsent[key] === true;
|
|
893
980
|
const disabled = config.readOnly;
|
|
894
981
|
return `
|
|
895
982
|
<div class="cc-category">
|
|
@@ -936,13 +1023,16 @@
|
|
|
936
1023
|
* Handle save preferences
|
|
937
1024
|
*/
|
|
938
1025
|
handleSave() {
|
|
939
|
-
var _a
|
|
1026
|
+
var _a;
|
|
940
1027
|
const checkboxes = (_a = this.element) === null || _a === void 0 ? void 0 : _a.querySelectorAll('input[data-category]');
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1028
|
+
// Initialize all configured categories to false
|
|
1029
|
+
const categories = { necessary: true, analytics: false, marketing: false };
|
|
1030
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
1031
|
+
if (key !== 'necessary') {
|
|
1032
|
+
categories[key] = false;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
// Override with actual checkbox values
|
|
946
1036
|
checkboxes === null || checkboxes === void 0 ? void 0 : checkboxes.forEach((checkbox) => {
|
|
947
1037
|
if (checkbox instanceof HTMLInputElement) {
|
|
948
1038
|
const category = checkbox.getAttribute('data-category');
|
|
@@ -952,22 +1042,16 @@
|
|
|
952
1042
|
}
|
|
953
1043
|
});
|
|
954
1044
|
this.eventEmitter.emit('consent:update', categories);
|
|
955
|
-
(_c = (_b = this.config).onChange) === null || _c === void 0 ? void 0 : _c.call(_b, categories);
|
|
956
1045
|
this.hide();
|
|
957
1046
|
}
|
|
958
1047
|
/**
|
|
959
1048
|
* Handle reject all
|
|
960
1049
|
*/
|
|
961
1050
|
handleRejectAll() {
|
|
962
|
-
|
|
963
|
-
const
|
|
964
|
-
necessary
|
|
965
|
-
|
|
966
|
-
marketing: false,
|
|
967
|
-
};
|
|
968
|
-
// Only add preferences if it's configured
|
|
969
|
-
if ((_a = this.config.categories) === null || _a === void 0 ? void 0 : _a.preferences) {
|
|
970
|
-
necessaryOnly.preferences = false;
|
|
1051
|
+
const necessaryOnly = { necessary: true, analytics: false, marketing: false };
|
|
1052
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
1053
|
+
if (key !== 'necessary')
|
|
1054
|
+
necessaryOnly[key] = false;
|
|
971
1055
|
}
|
|
972
1056
|
this.eventEmitter.emit('consent:reject', necessaryOnly);
|
|
973
1057
|
this.hide();
|
|
@@ -982,9 +1066,7 @@
|
|
|
982
1066
|
return;
|
|
983
1067
|
const firstFocusable = focusableElements[0];
|
|
984
1068
|
const lastFocusable = focusableElements[focusableElements.length - 1];
|
|
985
|
-
// Focus first element
|
|
986
1069
|
firstFocusable === null || firstFocusable === void 0 ? void 0 : firstFocusable.focus();
|
|
987
|
-
// Trap focus
|
|
988
1070
|
(_b = this.element) === null || _b === void 0 ? void 0 : _b.addEventListener('keydown', (e) => {
|
|
989
1071
|
if (e.key === 'Tab') {
|
|
990
1072
|
if (e.shiftKey && document.activeElement === firstFocusable) {
|
|
@@ -1072,7 +1154,7 @@
|
|
|
1072
1154
|
<div
|
|
1073
1155
|
class="cc-widget cc-widget--${escapeHtml(widgetPosition)} cc-widget--${escapeHtml(widgetStyle)}"
|
|
1074
1156
|
role="button"
|
|
1075
|
-
aria-label="${escapeHtml(translations.cookieSettings || '
|
|
1157
|
+
aria-label="${escapeHtml(translations.cookieSettings || 'Cookie settings')}"
|
|
1076
1158
|
tabindex="0"
|
|
1077
1159
|
data-theme="${escapeHtml(theme)}"
|
|
1078
1160
|
style="${colorStyle}"
|
|
@@ -1123,11 +1205,6 @@
|
|
|
1123
1205
|
|
|
1124
1206
|
/**
|
|
1125
1207
|
* GTMConsentMode - Full integration with Google Consent Mode v2
|
|
1126
|
-
*
|
|
1127
|
-
* Implements all required signals:
|
|
1128
|
-
* - ad_storage, ad_user_data, ad_personalization, analytics_storage (core GCM v2)
|
|
1129
|
-
* - functionality_storage, personalization_storage, security_storage (non-core)
|
|
1130
|
-
* - wait_for_update, url_passthrough, ads_data_redaction (advanced features)
|
|
1131
1208
|
*/
|
|
1132
1209
|
class GTMConsentMode {
|
|
1133
1210
|
constructor(dataLayerManager, config) {
|
|
@@ -1147,15 +1224,13 @@
|
|
|
1147
1224
|
analytics_storage: 'denied',
|
|
1148
1225
|
functionality_storage: 'denied',
|
|
1149
1226
|
personalization_storage: 'denied',
|
|
1150
|
-
security_storage: 'granted',
|
|
1227
|
+
security_storage: 'granted',
|
|
1151
1228
|
};
|
|
1152
|
-
// Add wait_for_update to give CMP time to restore returning visitor consent
|
|
1153
1229
|
const waitForUpdate = (_a = this.config.gtmWaitForUpdate) !== null && _a !== void 0 ? _a : 500;
|
|
1154
1230
|
if (waitForUpdate > 0) {
|
|
1155
1231
|
defaults['wait_for_update'] = waitForUpdate;
|
|
1156
1232
|
}
|
|
1157
1233
|
this.dataLayerManager.pushConsent('default', defaults);
|
|
1158
|
-
// Set advanced features via gtag('set', ...)
|
|
1159
1234
|
if (this.config.gtmUrlPassthrough) {
|
|
1160
1235
|
this.dataLayerManager.pushSet('url_passthrough', true);
|
|
1161
1236
|
}
|
|
@@ -1165,7 +1240,6 @@
|
|
|
1165
1240
|
}
|
|
1166
1241
|
/**
|
|
1167
1242
|
* Update consent state based on user choices
|
|
1168
|
-
* Called both on new consent and on page load for returning visitors
|
|
1169
1243
|
*/
|
|
1170
1244
|
updateConsent(categories) {
|
|
1171
1245
|
const gtmConsent = this.mapCategoriesToGTM(categories);
|
|
@@ -1175,14 +1249,19 @@
|
|
|
1175
1249
|
* Map consent categories to GTM Consent Mode v2 format
|
|
1176
1250
|
*/
|
|
1177
1251
|
mapCategoriesToGTM(categories) {
|
|
1252
|
+
// When preferences category is not configured, default functionality to granted
|
|
1253
|
+
const hasPreferencesCategory = 'preferences' in this.config.categories;
|
|
1254
|
+
const preferencesGranted = hasPreferencesCategory
|
|
1255
|
+
? categories.preferences === true
|
|
1256
|
+
: true;
|
|
1178
1257
|
return {
|
|
1179
1258
|
ad_storage: categories.marketing ? 'granted' : 'denied',
|
|
1180
1259
|
ad_user_data: categories.marketing ? 'granted' : 'denied',
|
|
1181
1260
|
ad_personalization: categories.marketing ? 'granted' : 'denied',
|
|
1182
1261
|
analytics_storage: categories.analytics ? 'granted' : 'denied',
|
|
1183
|
-
functionality_storage:
|
|
1184
|
-
personalization_storage:
|
|
1185
|
-
security_storage: 'granted',
|
|
1262
|
+
functionality_storage: preferencesGranted ? 'granted' : 'denied',
|
|
1263
|
+
personalization_storage: preferencesGranted ? 'granted' : 'denied',
|
|
1264
|
+
security_storage: 'granted',
|
|
1186
1265
|
};
|
|
1187
1266
|
}
|
|
1188
1267
|
}
|
|
@@ -1318,6 +1397,111 @@
|
|
|
1318
1397
|
}
|
|
1319
1398
|
}
|
|
1320
1399
|
|
|
1400
|
+
/**
|
|
1401
|
+
* Built-in translations for supported languages
|
|
1402
|
+
* Users can override any string via config.translations
|
|
1403
|
+
*/
|
|
1404
|
+
const en = {
|
|
1405
|
+
title: 'We use cookies',
|
|
1406
|
+
description: 'We use cookies to improve your experience on our site. You can choose which cookies you accept.',
|
|
1407
|
+
acceptAll: 'Accept all',
|
|
1408
|
+
rejectAll: 'Essentials only',
|
|
1409
|
+
customize: 'Customize',
|
|
1410
|
+
savePreferences: 'Save preferences',
|
|
1411
|
+
essentialsOnly: 'Essentials only',
|
|
1412
|
+
preferencesTitle: 'Cookie Preferences',
|
|
1413
|
+
cookieSettings: 'Cookie settings',
|
|
1414
|
+
cookies: 'Cookies',
|
|
1415
|
+
privacyPolicyLabel: 'Privacy Policy',
|
|
1416
|
+
};
|
|
1417
|
+
const fr = {
|
|
1418
|
+
title: 'Nous utilisons des cookies',
|
|
1419
|
+
description: 'Ce site utilise des cookies pour améliorer votre expérience de navigation. Vous pouvez choisir les cookies que vous acceptez.',
|
|
1420
|
+
acceptAll: 'Tout accepter',
|
|
1421
|
+
rejectAll: 'Essentiels uniquement',
|
|
1422
|
+
customize: 'Personnaliser',
|
|
1423
|
+
savePreferences: 'Enregistrer',
|
|
1424
|
+
essentialsOnly: 'Essentiels uniquement',
|
|
1425
|
+
preferencesTitle: 'Préférences des cookies',
|
|
1426
|
+
cookieSettings: 'Paramètres des cookies',
|
|
1427
|
+
cookies: 'Cookies',
|
|
1428
|
+
privacyPolicyLabel: 'Politique de confidentialité',
|
|
1429
|
+
};
|
|
1430
|
+
const de = {
|
|
1431
|
+
title: 'Wir verwenden Cookies',
|
|
1432
|
+
description: 'Diese Website verwendet Cookies, um Ihr Erlebnis zu verbessern. Sie können wählen, welche Cookies Sie akzeptieren.',
|
|
1433
|
+
acceptAll: 'Alle akzeptieren',
|
|
1434
|
+
rejectAll: 'Nur essenzielle',
|
|
1435
|
+
customize: 'Anpassen',
|
|
1436
|
+
savePreferences: 'Speichern',
|
|
1437
|
+
essentialsOnly: 'Nur essenzielle',
|
|
1438
|
+
preferencesTitle: 'Cookie-Einstellungen',
|
|
1439
|
+
cookieSettings: 'Cookie-Einstellungen',
|
|
1440
|
+
cookies: 'Cookies',
|
|
1441
|
+
privacyPolicyLabel: 'Datenschutzrichtlinie',
|
|
1442
|
+
};
|
|
1443
|
+
const es = {
|
|
1444
|
+
title: 'Usamos cookies',
|
|
1445
|
+
description: 'Este sitio utiliza cookies para mejorar su experiencia. Puede elegir qué cookies acepta.',
|
|
1446
|
+
acceptAll: 'Aceptar todo',
|
|
1447
|
+
rejectAll: 'Solo esenciales',
|
|
1448
|
+
customize: 'Personalizar',
|
|
1449
|
+
savePreferences: 'Guardar',
|
|
1450
|
+
essentialsOnly: 'Solo esenciales',
|
|
1451
|
+
preferencesTitle: 'Preferencias de cookies',
|
|
1452
|
+
cookieSettings: 'Configuración de cookies',
|
|
1453
|
+
cookies: 'Cookies',
|
|
1454
|
+
privacyPolicyLabel: 'Política de privacidad',
|
|
1455
|
+
};
|
|
1456
|
+
const it = {
|
|
1457
|
+
title: 'Utilizziamo i cookie',
|
|
1458
|
+
description: 'Questo sito utilizza i cookie per migliorare la tua esperienza. Puoi scegliere quali cookie accettare.',
|
|
1459
|
+
acceptAll: 'Accetta tutti',
|
|
1460
|
+
rejectAll: 'Solo essenziali',
|
|
1461
|
+
customize: 'Personalizza',
|
|
1462
|
+
savePreferences: 'Salva',
|
|
1463
|
+
essentialsOnly: 'Solo essenziali',
|
|
1464
|
+
preferencesTitle: 'Preferenze cookie',
|
|
1465
|
+
cookieSettings: 'Impostazioni cookie',
|
|
1466
|
+
cookies: 'Cookie',
|
|
1467
|
+
privacyPolicyLabel: 'Informativa sulla privacy',
|
|
1468
|
+
};
|
|
1469
|
+
const nl = {
|
|
1470
|
+
title: 'Wij gebruiken cookies',
|
|
1471
|
+
description: 'Deze site maakt gebruik van cookies om uw ervaring te verbeteren. U kunt kiezen welke cookies u accepteert.',
|
|
1472
|
+
acceptAll: 'Alles accepteren',
|
|
1473
|
+
rejectAll: 'Alleen essentieel',
|
|
1474
|
+
customize: 'Aanpassen',
|
|
1475
|
+
savePreferences: 'Opslaan',
|
|
1476
|
+
essentialsOnly: 'Alleen essentieel',
|
|
1477
|
+
preferencesTitle: 'Cookie-voorkeuren',
|
|
1478
|
+
cookieSettings: 'Cookie-instellingen',
|
|
1479
|
+
cookies: 'Cookies',
|
|
1480
|
+
privacyPolicyLabel: 'Privacybeleid',
|
|
1481
|
+
};
|
|
1482
|
+
const pt = {
|
|
1483
|
+
title: 'Utilizamos cookies',
|
|
1484
|
+
description: 'Este site utiliza cookies para melhorar a sua experiência. Pode escolher quais cookies aceita.',
|
|
1485
|
+
acceptAll: 'Aceitar todos',
|
|
1486
|
+
rejectAll: 'Apenas essenciais',
|
|
1487
|
+
customize: 'Personalizar',
|
|
1488
|
+
savePreferences: 'Guardar',
|
|
1489
|
+
essentialsOnly: 'Apenas essenciais',
|
|
1490
|
+
preferencesTitle: 'Preferências de cookies',
|
|
1491
|
+
cookieSettings: 'Definições de cookies',
|
|
1492
|
+
cookies: 'Cookies',
|
|
1493
|
+
privacyPolicyLabel: 'Política de privacidade',
|
|
1494
|
+
};
|
|
1495
|
+
const builtInTranslations = {
|
|
1496
|
+
en,
|
|
1497
|
+
fr,
|
|
1498
|
+
de,
|
|
1499
|
+
es,
|
|
1500
|
+
it,
|
|
1501
|
+
nl,
|
|
1502
|
+
pt,
|
|
1503
|
+
};
|
|
1504
|
+
|
|
1321
1505
|
/**
|
|
1322
1506
|
* CookieConsent - Main orchestrator class
|
|
1323
1507
|
*/
|
|
@@ -1327,6 +1511,16 @@
|
|
|
1327
1511
|
this.preferenceCenter = null;
|
|
1328
1512
|
this.floatingWidget = null;
|
|
1329
1513
|
this.gtmIntegration = null;
|
|
1514
|
+
this.hideTimeout = null;
|
|
1515
|
+
// SSR guard
|
|
1516
|
+
if (typeof window === 'undefined') {
|
|
1517
|
+
this.config = config;
|
|
1518
|
+
this.consentManager = null;
|
|
1519
|
+
this.storageManager = null;
|
|
1520
|
+
this.eventEmitter = null;
|
|
1521
|
+
this.scriptBlocker = null;
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1330
1524
|
this.config = this.validateConfig(config);
|
|
1331
1525
|
this.consentManager = new ConsentManager(this.config);
|
|
1332
1526
|
this.storageManager = new StorageManager();
|
|
@@ -1335,25 +1529,32 @@
|
|
|
1335
1529
|
if (this.config.gtmConsentMode) {
|
|
1336
1530
|
this.gtmIntegration = new GTMConsentMode(new DataLayerManager(), this.config);
|
|
1337
1531
|
}
|
|
1338
|
-
// Listen for
|
|
1339
|
-
this.eventEmitter.on('preferences:show', () => {
|
|
1340
|
-
this.showPreferences();
|
|
1341
|
-
});
|
|
1342
|
-
// Listen for consent updates
|
|
1532
|
+
// Listen for consent events — callbacks are fired AFTER consent is persisted
|
|
1343
1533
|
this.eventEmitter.on('consent:accept', (categories) => {
|
|
1534
|
+
var _a, _b;
|
|
1344
1535
|
this.updateConsent(categories);
|
|
1536
|
+
(_b = (_a = this.config).onAccept) === null || _b === void 0 ? void 0 : _b.call(_a, categories);
|
|
1345
1537
|
});
|
|
1346
1538
|
this.eventEmitter.on('consent:reject', (categories) => {
|
|
1539
|
+
var _a, _b;
|
|
1347
1540
|
this.updateConsent(categories);
|
|
1541
|
+
(_b = (_a = this.config).onReject) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1348
1542
|
});
|
|
1349
1543
|
this.eventEmitter.on('consent:update', (categories) => {
|
|
1544
|
+
var _a, _b;
|
|
1350
1545
|
this.updateConsent(categories);
|
|
1546
|
+
(_b = (_a = this.config).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, categories);
|
|
1547
|
+
});
|
|
1548
|
+
this.eventEmitter.on('preferences:show', () => {
|
|
1549
|
+
this.showPreferences();
|
|
1351
1550
|
});
|
|
1352
1551
|
}
|
|
1353
1552
|
/**
|
|
1354
1553
|
* Initialize the cookie consent system
|
|
1355
1554
|
*/
|
|
1356
1555
|
init() {
|
|
1556
|
+
if (typeof window === 'undefined')
|
|
1557
|
+
return;
|
|
1357
1558
|
// 1. Start blocking scripts immediately
|
|
1358
1559
|
this.scriptBlocker.init();
|
|
1359
1560
|
// 2. Set GTM default consent BEFORE checking storage
|
|
@@ -1363,35 +1564,28 @@
|
|
|
1363
1564
|
// 3. Check for existing consent
|
|
1364
1565
|
const storedConsent = this.storageManager.load();
|
|
1365
1566
|
if (storedConsent && !this.storageManager.isExpired(storedConsent)) {
|
|
1366
|
-
// Valid consent exists
|
|
1367
1567
|
if (this.consentManager.needsUpdate(storedConsent)) {
|
|
1368
|
-
// Policy updated, show banner again
|
|
1369
1568
|
if (this.config.autoShow) {
|
|
1370
1569
|
this.showBanner();
|
|
1371
1570
|
}
|
|
1372
1571
|
}
|
|
1373
1572
|
else {
|
|
1374
|
-
// Apply stored consent
|
|
1375
1573
|
this.applyConsent(storedConsent.categories);
|
|
1376
|
-
// Restore GTM consent for returning visitors (within wait_for_update window)
|
|
1377
1574
|
if (this.gtmIntegration) {
|
|
1378
1575
|
this.gtmIntegration.updateConsent(storedConsent.categories);
|
|
1379
1576
|
}
|
|
1380
1577
|
this.eventEmitter.emit('consent:load', storedConsent);
|
|
1381
|
-
// Show floating widget if enabled
|
|
1382
1578
|
if (this.config.showWidget) {
|
|
1383
1579
|
this.showFloatingWidget();
|
|
1384
1580
|
}
|
|
1385
1581
|
}
|
|
1386
1582
|
}
|
|
1387
1583
|
else {
|
|
1388
|
-
// No consent or expired
|
|
1389
1584
|
if (this.config.autoShow) {
|
|
1390
1585
|
this.showBanner();
|
|
1391
1586
|
}
|
|
1392
1587
|
}
|
|
1393
|
-
// Store instance globally
|
|
1394
|
-
// when used without a variable (e.g. new CookieConsent({}).init())
|
|
1588
|
+
// Store instance globally
|
|
1395
1589
|
window.cookieConsent = this;
|
|
1396
1590
|
this.eventEmitter.emit('consent:init');
|
|
1397
1591
|
}
|
|
@@ -1414,14 +1608,18 @@
|
|
|
1414
1608
|
showPreferences() {
|
|
1415
1609
|
var _a;
|
|
1416
1610
|
const stored = (_a = this.storageManager.load()) === null || _a === void 0 ? void 0 : _a.categories;
|
|
1417
|
-
// Default to all ON when no prior consent
|
|
1611
|
+
// Default to all ON when no prior consent
|
|
1418
1612
|
const currentConsent = stored || {
|
|
1419
1613
|
necessary: true,
|
|
1420
1614
|
analytics: true,
|
|
1421
1615
|
marketing: true,
|
|
1422
|
-
preferences: true,
|
|
1423
1616
|
};
|
|
1424
|
-
//
|
|
1617
|
+
// Add any configured categories not in current consent
|
|
1618
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
1619
|
+
if (!(key in currentConsent)) {
|
|
1620
|
+
currentConsent[key] = key === 'necessary';
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1425
1623
|
if (this.preferenceCenter) {
|
|
1426
1624
|
this.preferenceCenter.destroy();
|
|
1427
1625
|
}
|
|
@@ -1438,11 +1636,11 @@
|
|
|
1438
1636
|
if (this.gtmIntegration) {
|
|
1439
1637
|
this.gtmIntegration.updateConsent(categories);
|
|
1440
1638
|
}
|
|
1441
|
-
// Show floating widget after consent is given
|
|
1639
|
+
// Show floating widget after consent is given
|
|
1442
1640
|
if (this.config.showWidget) {
|
|
1443
1641
|
setTimeout(() => {
|
|
1444
1642
|
this.showFloatingWidget();
|
|
1445
|
-
}, 400);
|
|
1643
|
+
}, 400);
|
|
1446
1644
|
}
|
|
1447
1645
|
}
|
|
1448
1646
|
/**
|
|
@@ -1455,14 +1653,19 @@
|
|
|
1455
1653
|
* Reset consent (clear stored data and show banner)
|
|
1456
1654
|
*/
|
|
1457
1655
|
reset() {
|
|
1656
|
+
var _a, _b;
|
|
1458
1657
|
this.storageManager.clear();
|
|
1459
1658
|
this.scriptBlocker.block();
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1659
|
+
const denied = { necessary: true, analytics: false, marketing: false };
|
|
1660
|
+
for (const key of Object.keys(this.config.categories)) {
|
|
1661
|
+
if (key !== 'necessary')
|
|
1662
|
+
denied[key] = false;
|
|
1663
|
+
}
|
|
1664
|
+
clearDeniedCookies(denied);
|
|
1463
1665
|
if (this.gtmIntegration) {
|
|
1464
|
-
this.gtmIntegration.updateConsent(
|
|
1666
|
+
this.gtmIntegration.updateConsent(denied);
|
|
1465
1667
|
}
|
|
1668
|
+
(_b = (_a = this.config).onReject) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1466
1669
|
this.showBanner();
|
|
1467
1670
|
}
|
|
1468
1671
|
/**
|
|
@@ -1482,6 +1685,10 @@
|
|
|
1482
1685
|
*/
|
|
1483
1686
|
destroy() {
|
|
1484
1687
|
var _a, _b, _c, _d;
|
|
1688
|
+
if (this.hideTimeout) {
|
|
1689
|
+
clearTimeout(this.hideTimeout);
|
|
1690
|
+
this.hideTimeout = null;
|
|
1691
|
+
}
|
|
1485
1692
|
(_a = this.banner) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
1486
1693
|
this.banner = null;
|
|
1487
1694
|
(_b = this.preferenceCenter) === null || _b === void 0 ? void 0 : _b.destroy();
|
|
@@ -1489,6 +1696,10 @@
|
|
|
1489
1696
|
(_c = this.floatingWidget) === null || _c === void 0 ? void 0 : _c.destroy();
|
|
1490
1697
|
this.floatingWidget = null;
|
|
1491
1698
|
(_d = this.scriptBlocker) === null || _d === void 0 ? void 0 : _d.destroy();
|
|
1699
|
+
this.eventEmitter.clear();
|
|
1700
|
+
if (window.cookieConsent === this) {
|
|
1701
|
+
window.cookieConsent = undefined;
|
|
1702
|
+
}
|
|
1492
1703
|
}
|
|
1493
1704
|
/**
|
|
1494
1705
|
* Show the banner
|
|
@@ -1514,33 +1725,37 @@
|
|
|
1514
1725
|
*/
|
|
1515
1726
|
applyConsent(categories) {
|
|
1516
1727
|
this.scriptBlocker.unblock(categories);
|
|
1517
|
-
// CNIL/GDPR: actively delete cookies for denied categories
|
|
1518
1728
|
clearDeniedCookies(categories);
|
|
1519
1729
|
}
|
|
1520
1730
|
/**
|
|
1521
1731
|
* Validate and set default config values
|
|
1522
1732
|
*/
|
|
1523
1733
|
validateConfig(config) {
|
|
1524
|
-
|
|
1734
|
+
var _a;
|
|
1735
|
+
// Merge built-in language translations with user overrides
|
|
1736
|
+
const langKey = ((_a = config.language) === null || _a === void 0 ? void 0 : _a.toLowerCase().split('-')[0]) || 'en';
|
|
1737
|
+
const langDefaults = builtInTranslations[langKey] || builtInTranslations['en'];
|
|
1738
|
+
const mergedTranslations = Object.assign(Object.assign({}, langDefaults), config.translations);
|
|
1739
|
+
return Object.assign(Object.assign({}, config), { translations: mergedTranslations, categories: config.categories || {
|
|
1525
1740
|
necessary: {
|
|
1526
1741
|
enabled: true,
|
|
1527
1742
|
readOnly: true,
|
|
1528
|
-
label: '
|
|
1529
|
-
description: '
|
|
1743
|
+
label: 'Essential',
|
|
1744
|
+
description: 'Required for the website to function properly.',
|
|
1530
1745
|
},
|
|
1531
1746
|
analytics: {
|
|
1532
1747
|
enabled: true,
|
|
1533
1748
|
readOnly: false,
|
|
1534
|
-
label: '
|
|
1535
|
-
description: '
|
|
1749
|
+
label: 'Analytics',
|
|
1750
|
+
description: 'Help us understand how you use our site.',
|
|
1536
1751
|
},
|
|
1537
1752
|
marketing: {
|
|
1538
1753
|
enabled: true,
|
|
1539
1754
|
readOnly: false,
|
|
1540
1755
|
label: 'Marketing',
|
|
1541
|
-
description: '
|
|
1756
|
+
description: 'Used to deliver relevant advertisements.',
|
|
1542
1757
|
},
|
|
1543
|
-
}, mode: config.mode || 'opt-in', autoShow: config.autoShow !== undefined ? config.autoShow : true, revision: config.revision || 1, gtmConsentMode: config.gtmConsentMode
|
|
1758
|
+
}, 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' });
|
|
1544
1759
|
}
|
|
1545
1760
|
}
|
|
1546
1761
|
|