@el7ven/cookie-kit 0.2.20 → 0.2.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@el7ven/cookie-kit",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "module": "./src/index.js",
@@ -11,8 +11,10 @@
11
11
  "./dist/styles": "./dist/styles/index.css",
12
12
  "./react": "./src/react/index.js",
13
13
  "./vue": "./src/vue/index.js",
14
- "./src/composables/useCookieConsent": "./src/vue/composables/useCookieConsent.js",
14
+ "./analytics": "./src/core/analytics.js",
15
+ "./config": "./src/core/index.js",
15
16
  "./composables/useCookieConsent": "./src/vue/composables/useCookieConsent.js",
17
+ "./src/composables/useCookieConsent": "./src/vue/composables/useCookieConsent.js",
16
18
  "./package.json": "./package.json"
17
19
  },
18
20
  "files": [
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @el7ven/cookie-kit - Analytics Integration Module
3
+ * Google Consent Mode v2, GTM, GA4, Meta Pixel, Yandex Metrica
4
+ */
5
+
6
+ /**
7
+ * Initialize Google Consent Mode v2 with default denied state
8
+ */
9
+ export function initGoogleConsentMode() {
10
+ if (typeof window === 'undefined') return
11
+
12
+ window.dataLayer = window.dataLayer || []
13
+ window.gtag = window.gtag || function gtag() { window.dataLayer.push(arguments) }
14
+
15
+ // Set default consent state - all denied until user consents
16
+ window.gtag('consent', 'default', {
17
+ ad_storage: 'denied',
18
+ ad_user_data: 'denied',
19
+ ad_personalization: 'denied',
20
+ analytics_storage: 'denied',
21
+ functionality_storage: 'denied',
22
+ personalization_storage: 'denied',
23
+ security_storage: 'granted' // Always granted for security
24
+ })
25
+ }
26
+
27
+ /**
28
+ * Update Google Consent Mode based on cookie consent categories
29
+ * @param {Object} categories - { analytics: true, marketing: false, preferences: true }
30
+ */
31
+ export function updateGoogleConsent(categories = {}) {
32
+ if (typeof window === 'undefined' || !window.gtag) return
33
+
34
+ const consentUpdate = {
35
+ analytics_storage: categories.analytics ? 'granted' : 'denied',
36
+ ad_storage: categories.marketing ? 'granted' : 'denied',
37
+ ad_user_data: categories.marketing ? 'granted' : 'denied',
38
+ ad_personalization: categories.marketing ? 'granted' : 'denied',
39
+ functionality_storage: categories.preferences ? 'granted' : 'denied',
40
+ personalization_storage: categories.preferences ? 'granted' : 'denied',
41
+ security_storage: 'granted'
42
+ }
43
+
44
+ window.gtag('consent', 'update', consentUpdate)
45
+ }
46
+
47
+ /**
48
+ * Load Google Tag Manager
49
+ * @param {string} gtmId - GTM container ID (e.g., 'GTM-XXXXX')
50
+ */
51
+ export function loadGTM(gtmId) {
52
+ if (!gtmId || typeof document === 'undefined') return
53
+
54
+ const script = document.createElement('script')
55
+ script.async = true
56
+ script.src = `https://www.googletagmanager.com/gtm.js?id=${gtmId}`
57
+ document.head.appendChild(script)
58
+
59
+ window.dataLayer = window.dataLayer || []
60
+ window.dataLayer.push({
61
+ 'gtm.start': new Date().getTime(),
62
+ event: 'gtm.js'
63
+ })
64
+ }
65
+
66
+ /**
67
+ * Load Google Analytics 4
68
+ * @param {string} ga4Id - GA4 measurement ID (e.g., 'G-XXXXX')
69
+ */
70
+ export function loadGA4(ga4Id) {
71
+ if (!ga4Id || typeof document === 'undefined') return
72
+
73
+ const script = document.createElement('script')
74
+ script.async = true
75
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${ga4Id}`
76
+ document.head.appendChild(script)
77
+
78
+ window.dataLayer = window.dataLayer || []
79
+ window.gtag = window.gtag || function gtag() { window.dataLayer.push(arguments) }
80
+ window.gtag('js', new Date())
81
+ window.gtag('config', ga4Id)
82
+ }
83
+
84
+ /**
85
+ * Load Meta (Facebook) Pixel
86
+ * @param {string} pixelId - Meta Pixel ID
87
+ */
88
+ export function loadMetaPixel(pixelId) {
89
+ if (!pixelId || typeof window === 'undefined') return
90
+
91
+ /* eslint-disable */
92
+ !function(f,b,e,v,n,t,s)
93
+ {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
94
+ n.callMethod.apply(n,arguments):n.queue.push(arguments)};
95
+ if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
96
+ n.queue=[];t=b.createElement(e);t.async=!0;
97
+ t.src=v;s=b.getElementsByTagName(e)[0];
98
+ s.parentNode.insertBefore(t,s)}(window, document,'script',
99
+ 'https://connect.facebook.net/en_US/fbevents.js');
100
+ /* eslint-enable */
101
+
102
+ window.fbq('init', pixelId)
103
+ window.fbq('track', 'PageView')
104
+ }
105
+
106
+ /**
107
+ * Load Yandex Metrica
108
+ * @param {string} counterId - Yandex Metrica counter ID
109
+ */
110
+ export function loadYandexMetrica(counterId) {
111
+ if (!counterId || typeof document === 'undefined') return
112
+
113
+ /* eslint-disable */
114
+ (function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
115
+ m[i].l=1*new Date();
116
+ for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
117
+ k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
118
+ (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
119
+ /* eslint-enable */
120
+
121
+ window.ym(counterId, 'init', {
122
+ clickmap: true,
123
+ trackLinks: true,
124
+ accurateTrackBounce: true,
125
+ webvisor: true
126
+ })
127
+ }
128
+
129
+ /**
130
+ * Block/unblock scripts based on consent categories
131
+ * Scripts with data-cookie-category attribute will be managed
132
+ * @param {Object} categories - { analytics: true, marketing: false }
133
+ */
134
+ export function manageScriptBlocking(categories = {}) {
135
+ if (typeof document === 'undefined') return
136
+
137
+ document.querySelectorAll('script[data-cookie-category]').forEach(script => {
138
+ const category = script.getAttribute('data-cookie-category')
139
+ if (categories[category]) {
140
+ // Re-enable script
141
+ if (script.type === 'text/plain' && script.dataset.originalSrc) {
142
+ const newScript = document.createElement('script')
143
+ newScript.src = script.dataset.originalSrc
144
+ newScript.async = true
145
+ script.parentNode.replaceChild(newScript, script)
146
+ }
147
+ }
148
+ })
149
+ }
150
+
151
+ /**
152
+ * Create analytics manager that auto-integrates with cookie consent
153
+ * @param {Object} config - { ga4: 'G-XXX', gtm: 'GTM-XXX', metaPixel: 'XXX', yandexMetrica: 'XXX' }
154
+ * @returns {Object} Analytics manager instance
155
+ */
156
+ export function createAnalyticsManager(config = {}) {
157
+ let initialized = false
158
+
159
+ const init = () => {
160
+ if (initialized) return
161
+ initialized = true
162
+
163
+ // Always init Google Consent Mode first (denied by default)
164
+ initGoogleConsentMode()
165
+
166
+ // Load GTM if configured (it respects consent mode)
167
+ if (config.gtm) loadGTM(config.gtm)
168
+ }
169
+
170
+ const onConsentUpdate = (categories = {}) => {
171
+ // Update Google Consent Mode
172
+ updateGoogleConsent(categories)
173
+
174
+ // Load analytics scripts based on consent
175
+ if (categories.analytics) {
176
+ if (config.ga4) loadGA4(config.ga4)
177
+ if (config.yandexMetrica) loadYandexMetrica(config.yandexMetrica)
178
+ }
179
+
180
+ if (categories.marketing) {
181
+ if (config.metaPixel) loadMetaPixel(config.metaPixel)
182
+ }
183
+
184
+ // Manage script blocking
185
+ manageScriptBlocking(categories)
186
+ }
187
+
188
+ const connectToCookieConsent = () => {
189
+ if (typeof window === 'undefined') return
190
+
191
+ // Wait for CookieConsent to be available
192
+ const checkInterval = setInterval(() => {
193
+ if (window.CookieConsent) {
194
+ clearInterval(checkInterval)
195
+
196
+ // Check if consent already given
197
+ const currentConsent = window.CookieConsent.getConsent()
198
+ if (currentConsent?.categories) {
199
+ onConsentUpdate(currentConsent.categories)
200
+ }
201
+
202
+ // Listen for future changes
203
+ window.CookieConsent.on('consentChanged', ({ consent }) => {
204
+ if (consent?.categories) {
205
+ onConsentUpdate(consent.categories)
206
+ }
207
+ })
208
+ }
209
+ }, 100)
210
+
211
+ // Stop checking after 10 seconds
212
+ setTimeout(() => clearInterval(checkInterval), 10000)
213
+ }
214
+
215
+ return {
216
+ init,
217
+ onConsentUpdate,
218
+ connectToCookieConsent
219
+ }
220
+ }
package/src/index.js CHANGED
@@ -8,8 +8,12 @@
8
8
  // Core functionality
9
9
  export { createCookieKit, CookieKitCore, DEFAULT_CONFIG } from './core/index.js'
10
10
 
11
+ // Analytics integration
12
+ export { createAnalyticsManager, initGoogleConsentMode, updateGoogleConsent, loadGTM, loadGA4, loadMetaPixel, loadYandexMetrica, manageScriptBlocking } from './core/analytics.js'
13
+
11
14
  // Import for default export
12
15
  import { createCookieKit, CookieKitCore, DEFAULT_CONFIG } from './core/index.js'
16
+ import { createAnalyticsManager } from './core/analytics.js'
13
17
  import { initCookieKit } from './js/index.js'
14
18
  import { VERSION } from './core/version.js'
15
19
 
@@ -62,6 +66,7 @@ export function autoCookieConsent(config = {}) {
62
66
  export default {
63
67
  createCookieKit,
64
68
  createCookieConsent,
69
+ createAnalyticsManager,
65
70
  autoCookieConsent,
66
71
  initCookieKit,
67
72
  CookieKitCore,
@@ -1,13 +1,12 @@
1
1
  <template>
2
2
  <div class="cookie-consent" :data-cookie-kit-theme="theme" :style="themeVarsStyle">
3
- <!-- Cookie Drawer (Unified Component) -->
4
3
  <CookieDrawer
5
4
  ref="drawerRef"
6
5
  :is-visible="isVisible"
7
6
  :is-settings-mode="isSettingsMode"
8
7
  :current-tab="currentTab"
9
8
  :categories="categories"
10
- :config="config"
9
+ :config="mergedConfig"
11
10
  :consent-version="consentVersion"
12
11
  :capabilities="capabilities"
13
12
  :is-v2="isV2"
@@ -23,88 +22,129 @@
23
22
  </template>
24
23
 
25
24
  <script setup>
26
- import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
25
+ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
27
26
  import { useCookieKitVue, DEFAULT_CONFIG } from './index.js'
28
27
  import CookieDrawer from './CookieDrawer.vue'
29
28
 
30
- // Props for customization
31
29
  const props = defineProps({
32
30
  config: {
33
31
  type: Object,
34
- default: () => DEFAULT_CONFIG
32
+ default: () => ({})
35
33
  }
36
34
  })
37
35
 
38
- // Use the Vue composable
36
+ const emit = defineEmits(['consentChanged', 'consentCleared'])
37
+
38
+ // Deep merge user config with defaults
39
+ const mergedConfig = computed(() => deepMerge(DEFAULT_CONFIG, props.config))
40
+
41
+ // Use the Vue composable with merged config
39
42
  const {
43
+ core,
40
44
  consent,
45
+ categories: coreCategories,
41
46
  acceptAll,
42
47
  rejectAll,
43
48
  acceptSelected,
44
49
  hasConsented,
45
- hasCategoryConsent
46
- } = useCookieKitVue(props.config)
50
+ hasCategoryConsent,
51
+ resetConsent
52
+ } = useCookieKitVue(mergedConfig.value)
47
53
 
48
- // Local state
49
- const isVisible = computed(() => !hasConsented.value)
54
+ // Local UI state
55
+ const isVisible = ref(false)
50
56
  const isSettingsMode = ref(false)
51
57
  const currentTab = ref('privacy')
52
- const categories = computed(() => consent.value?.categories || props.config.categories)
53
- const consentVersion = computed(() => props.config.version || '1.0.0')
54
- const capabilities = computed(() => ({}))
55
- const isV2 = computed(() => true)
56
- const theme = computed(() => props.config.theme || 'light')
58
+ const drawerRef = ref(null)
59
+
60
+ // Category state (reactive, for toggles)
61
+ const categories = ref(buildCategoryState(mergedConfig.value.categories))
62
+
63
+ // Computed
64
+ const consentVersion = computed(() => mergedConfig.value.version || 'v2')
65
+ const capabilities = computed(() => mergedConfig.value.capabilities || {})
66
+ const isV2 = computed(() => consentVersion.value === 'v2')
67
+ const theme = computed(() => mergedConfig.value.theme || 'light')
57
68
  const themeVarsStyle = computed(() => {
58
- const vars = props.config.themeVars || {}
69
+ const vars = mergedConfig.value.themeVars || {}
59
70
  return Object.fromEntries(
60
- Object.entries(vars).map(([key, value]) => [key.startsWith('--') ? key : `--${key}`, value])
71
+ Object.entries(vars).map(([k, v]) => [k.startsWith('--') ? k : `--${k}`, v])
61
72
  )
62
73
  })
63
- const drawerRef = ref(null)
64
74
 
65
75
  const enabledSettingsTabs = computed(() => {
66
- return Object.keys(props.config.categories || {}).filter(categoryId => {
67
- return props.config.categories[categoryId]?.enabled
68
- })
76
+ return Object.keys(mergedConfig.value.categories || {}).filter(id =>
77
+ mergedConfig.value.categories[id]?.enabled
78
+ )
69
79
  })
70
80
 
71
- const openSettings = () => {
72
- isSettingsMode.value = true
73
- }
74
-
75
- const closeSettings = () => {
76
- if (consent.value?.hasConsented) {
77
- return
81
+ // Show/hide logic
82
+ onMounted(() => {
83
+ if (!hasConsented.value && mergedConfig.value.autoShow !== false) {
84
+ isVisible.value = true
78
85
  }
79
- isSettingsMode.value = false
80
- }
81
86
 
82
- const acceptSelection = () => {
83
- // Get selected category IDs
84
- const selectedIds = Object.keys(categories.value)
85
- .filter(id => categories.value[id]?.enabled)
86
-
87
- acceptSelected(selectedIds)
88
- }
87
+ // Listen for consent events from core
88
+ if (core) {
89
+ core.on('consentChanged', (data) => {
90
+ emit('consentChanged', data)
91
+ })
92
+ core.on('consentCleared', () => {
93
+ isVisible.value = true
94
+ emit('consentCleared')
95
+ })
96
+ }
89
97
 
90
- const selectTab = (tabId) => {
91
- currentTab.value = tabId
92
- }
98
+ // Expose global API
99
+ if (typeof window !== 'undefined' && mergedConfig.value.mountGlobal !== false) {
100
+ window.CookieConsent = {
101
+ hasConsent: () => hasConsented.value,
102
+ hasCategoryConsent: (cat) => hasCategoryConsent(cat),
103
+ getConsent: () => consent.value,
104
+ acceptAll: () => handleAcceptAll(),
105
+ rejectAll: () => handleRejectAll(),
106
+ acceptSelected: (ids) => acceptSelected(ids),
107
+ clearConsent: () => { resetConsent(); isVisible.value = true },
108
+ show: () => { isVisible.value = true },
109
+ showSettings: () => { isSettingsMode.value = true; isVisible.value = true },
110
+ on: (event, cb) => core?.on(event, cb),
111
+ off: (event, cb) => core?.off(event, cb)
112
+ }
113
+ }
114
+ })
93
115
 
116
+ // Focus trap
94
117
  const getDrawerDialogElement = () => {
95
118
  const exposed = drawerRef.value?.dialogElement
96
- const result = typeof exposed === 'function' ? exposed() : exposed?.value || exposed || null
97
- return result
119
+ return typeof exposed === 'function' ? exposed() : exposed?.value || exposed || null
98
120
  }
99
121
 
100
- // Focus trap logic (simplified)
122
+ let previousActiveElement = null
123
+
101
124
  watch(isVisible, async (visible) => {
102
125
  if (visible) {
126
+ previousActiveElement = document.activeElement
127
+ await nextTick()
128
+ await new Promise(resolve => setTimeout(resolve, 50))
129
+ const el = getDrawerDialogElement()
130
+ if (el) {
131
+ const focusable = el.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
132
+ if (focusable.length) focusable[0].focus()
133
+ }
134
+ } else {
135
+ if (previousActiveElement?.focus) previousActiveElement.focus()
136
+ previousActiveElement = null
137
+ }
138
+ })
139
+
140
+ watch([isSettingsMode, currentTab], async () => {
141
+ if (isVisible.value) {
103
142
  await nextTick()
104
143
  await new Promise(resolve => setTimeout(resolve, 50))
105
- const dialogElement = getDrawerDialogElement()
106
- if (dialogElement && props.config.debug) {
107
- console.log('[CookieConsent] Focus trap activated')
144
+ const el = getDrawerDialogElement()
145
+ if (el) {
146
+ const focusable = el.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
147
+ if (focusable.length) focusable[0].focus()
108
148
  }
109
149
  }
110
150
  })
@@ -112,58 +152,121 @@ watch(isVisible, async (visible) => {
112
152
  // Event handlers
113
153
  const handleAcceptAll = () => {
114
154
  acceptAll()
155
+ isVisible.value = false
156
+ isSettingsMode.value = false
115
157
  }
116
158
 
117
159
  const handleRejectAll = () => {
118
160
  rejectAll()
161
+ isVisible.value = false
162
+ isSettingsMode.value = false
119
163
  }
120
164
 
121
165
  const handleAcceptSelection = () => {
122
- acceptSelection()
166
+ const mode = mergedConfig.value.mode
167
+
168
+ if (mode === 'essential') {
169
+ handleAcceptAll()
170
+ return
171
+ }
172
+
173
+ if (!isSettingsMode.value) {
174
+ const selectedIds = Object.keys(categories.value).filter(id => categories.value[id])
175
+ acceptSelected(selectedIds)
176
+ isVisible.value = false
177
+ return
178
+ }
179
+
180
+ // Settings mode: navigate tabs or save
181
+ const tabs = enabledSettingsTabs.value
182
+ if (currentTab.value === 'privacy' && tabs.length) {
183
+ currentTab.value = tabs[0]
184
+ return
185
+ }
186
+
187
+ const idx = tabs.indexOf(currentTab.value)
188
+ if (idx >= 0 && idx < tabs.length - 1) {
189
+ currentTab.value = tabs[idx + 1]
190
+ return
191
+ }
192
+
193
+ const selectedIds = Object.keys(categories.value).filter(id => categories.value[id])
194
+ acceptSelected(selectedIds)
195
+ isVisible.value = false
196
+ isSettingsMode.value = false
123
197
  }
124
198
 
125
199
  const handleOpenSettings = () => {
126
- openSettings()
200
+ isSettingsMode.value = true
127
201
  }
128
202
 
129
203
  const handleSelectTab = (tabId) => {
130
- selectTab(tabId)
204
+ currentTab.value = tabId
131
205
  }
132
206
 
133
207
  const handleToggleCategory = (categoryId) => {
134
- const current = categories.value[categoryId]
135
- if (current) {
136
- categories.value[categoryId] = { ...current, enabled: !current.enabled }
208
+ const cat = mergedConfig.value.categories[categoryId]
209
+ if (cat && !cat.locked && !cat.required) {
210
+ categories.value[categoryId] = !categories.value[categoryId]
137
211
  }
138
212
  }
139
213
 
140
214
  const handleClose = () => {
141
215
  if (isSettingsMode.value) {
142
- closeSettings()
216
+ if (hasConsented.value) {
217
+ isVisible.value = false
218
+ isSettingsMode.value = false
219
+ } else {
220
+ isSettingsMode.value = false
221
+ }
222
+ } else {
223
+ if (mergedConfig.value.mode === 'essential') {
224
+ handleAcceptAll()
225
+ }
143
226
  }
144
227
  }
145
228
 
146
- // Cleanup on unmount
147
229
  onUnmounted(() => {
148
- // Add cleanup if needed
230
+ if (typeof document !== 'undefined') {
231
+ document.body.style.overflow = ''
232
+ }
233
+ if (typeof window !== 'undefined') {
234
+ delete window.CookieConsent
235
+ }
149
236
  })
150
237
 
151
238
  // Expose methods for external use
152
239
  defineExpose({
153
240
  acceptAll: handleAcceptAll,
154
241
  rejectAll: handleRejectAll,
155
- resetConsent: () => {
156
- // Reset logic if needed
157
- }
242
+ resetConsent,
243
+ show: () => { isVisible.value = true },
244
+ showSettings: () => { isSettingsMode.value = true; isVisible.value = true }
158
245
  })
246
+
247
+ // Helpers
248
+ function buildCategoryState(cats) {
249
+ if (!cats) return {}
250
+ return Object.fromEntries(
251
+ Object.keys(cats).map(id => [id, !!cats[id].enabled])
252
+ )
253
+ }
254
+
255
+ function deepMerge(target, source) {
256
+ const result = { ...target }
257
+ for (const key of Object.keys(source)) {
258
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
259
+ result[key] = deepMerge(target[key] || {}, source[key])
260
+ } else {
261
+ result[key] = source[key]
262
+ }
263
+ }
264
+ return result
265
+ }
159
266
  </script>
160
267
 
161
268
  <style lang="scss" scoped>
162
269
  .cookie-consent {
163
270
  position: relative;
164
-
165
- &--has-modal {
166
- min-height: 100vh;
167
- }
168
271
  }
169
272
  </style>
@@ -0,0 +1,168 @@
1
+ <template>
2
+ <div v-if="shouldShow" class="cookie-consent-debug">
3
+ <button
4
+ @click="showPanel = !showPanel"
5
+ class="cookie-consent-debug__button"
6
+ title="Cookie Consent Debug"
7
+ >
8
+ 🍪
9
+ </button>
10
+
11
+ <div v-if="showPanel" class="cookie-consent-debug__panel">
12
+ <div class="cookie-consent-debug__header">
13
+ <h4>Cookie Consent Debug</h4>
14
+ <button @click="showPanel = false" class="cookie-consent-debug__close">&times;</button>
15
+ </div>
16
+ <div class="cookie-consent-debug__info">
17
+ <p><strong>Status:</strong> {{ consentStatus }}</p>
18
+ <p v-for="(status, cat) in categoryStatuses" :key="cat">
19
+ <strong>{{ cat }}:</strong> {{ status }}
20
+ </p>
21
+ </div>
22
+
23
+ <div class="cookie-consent-debug__actions">
24
+ <button @click="doAcceptAll" class="cookie-consent-debug__btn cookie-consent-debug__btn--accept">Accept All</button>
25
+ <button @click="doRejectAll" class="cookie-consent-debug__btn cookie-consent-debug__btn--reject">Reject All</button>
26
+ <button @click="doClear" class="cookie-consent-debug__btn cookie-consent-debug__btn--clear">Clear</button>
27
+ <button @click="doShow" class="cookie-consent-debug__btn cookie-consent-debug__btn--show">Show Banner</button>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <script setup>
34
+ import { ref, computed } from 'vue'
35
+
36
+ const props = defineProps({
37
+ debug: { type: Boolean, default: false },
38
+ categories: { type: Array, default: () => ['necessary', 'analytics', 'marketing', 'preferences'] }
39
+ })
40
+
41
+ const showPanel = ref(false)
42
+
43
+ const shouldShow = computed(() => {
44
+ if (props.debug) return true
45
+ if (typeof window === 'undefined') return false
46
+ const host = window.location?.hostname || ''
47
+ return host === 'localhost' || host.includes('test') || host.includes('.local')
48
+ })
49
+
50
+ const consentStatus = computed(() => {
51
+ return window.CookieConsent?.hasConsent() ? 'Given' : 'Not given'
52
+ })
53
+
54
+ const categoryStatuses = computed(() => {
55
+ const result = {}
56
+ props.categories.forEach(cat => {
57
+ result[cat] = window.CookieConsent?.hasCategoryConsent(cat) ? 'Enabled' : 'Disabled'
58
+ })
59
+ return result
60
+ })
61
+
62
+ const doAcceptAll = () => window.CookieConsent?.acceptAll()
63
+ const doRejectAll = () => window.CookieConsent?.rejectAll()
64
+ const doClear = () => window.CookieConsent?.clearConsent()
65
+ const doShow = () => window.CookieConsent?.show()
66
+ </script>
67
+
68
+ <style scoped>
69
+ .cookie-consent-debug {
70
+ position: fixed;
71
+ bottom: 20px;
72
+ right: 20px;
73
+ z-index: 10000;
74
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
75
+ }
76
+
77
+ .cookie-consent-debug__button {
78
+ width: 40px;
79
+ height: 40px;
80
+ border-radius: 50%;
81
+ background: #0026aa;
82
+ color: white;
83
+ border: none;
84
+ cursor: pointer;
85
+ font-size: 16px;
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
90
+ transition: all 0.2s;
91
+ }
92
+
93
+ .cookie-consent-debug__button:hover {
94
+ background: #001d88;
95
+ transform: scale(1.1);
96
+ }
97
+
98
+ .cookie-consent-debug__panel {
99
+ position: absolute;
100
+ bottom: 50px;
101
+ right: 0;
102
+ background: white;
103
+ border: 1px solid #e0e0e0;
104
+ border-radius: 8px;
105
+ padding: 16px;
106
+ min-width: 250px;
107
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
108
+ }
109
+
110
+ .cookie-consent-debug__header {
111
+ display: flex;
112
+ justify-content: space-between;
113
+ align-items: center;
114
+ margin-bottom: 12px;
115
+ }
116
+
117
+ .cookie-consent-debug__header h4 {
118
+ margin: 0;
119
+ font-size: 14px;
120
+ font-weight: 600;
121
+ color: #333;
122
+ }
123
+
124
+ .cookie-consent-debug__close {
125
+ background: none;
126
+ border: none;
127
+ font-size: 18px;
128
+ cursor: pointer;
129
+ color: #666;
130
+ padding: 0 4px;
131
+ }
132
+
133
+ .cookie-consent-debug__info p {
134
+ margin: 4px 0;
135
+ font-size: 12px;
136
+ color: #666;
137
+ }
138
+
139
+ .cookie-consent-debug__actions {
140
+ display: flex;
141
+ flex-direction: column;
142
+ gap: 6px;
143
+ margin-top: 12px;
144
+ }
145
+
146
+ .cookie-consent-debug__btn {
147
+ padding: 6px 12px;
148
+ border: none;
149
+ border-radius: 4px;
150
+ font-size: 11px;
151
+ cursor: pointer;
152
+ transition: all 0.2s;
153
+ color: white;
154
+ }
155
+
156
+ .cookie-consent-debug__btn--accept { background: #28a745; }
157
+ .cookie-consent-debug__btn--accept:hover { background: #218838; }
158
+ .cookie-consent-debug__btn--reject { background: #dc3545; }
159
+ .cookie-consent-debug__btn--reject:hover { background: #c82333; }
160
+ .cookie-consent-debug__btn--clear { background: #ffc107; color: #212529; }
161
+ .cookie-consent-debug__btn--clear:hover { background: #e0a800; }
162
+ .cookie-consent-debug__btn--show { background: #17a2b8; }
163
+ .cookie-consent-debug__btn--show:hover { background: #138496; }
164
+
165
+ @media (max-width: 767px) {
166
+ .cookie-consent-debug { display: none; }
167
+ }
168
+ </style>
@@ -17,17 +17,17 @@
17
17
  <div ref="dialogRef" class="cookie-drawer__content" role="dialog" aria-modal="true" @click.stop>
18
18
  <!-- Header -->
19
19
  <div class="cookie-drawer__header">
20
- <h3 class="cookie-drawer__title">
20
+ <div class="cookie-drawer__title">
21
21
  {{ isSettingsMode
22
22
  ? (config.texts?.settings?.title || 'Cookie Settings')
23
23
  : (config.mode === 'essential'
24
24
  ? (config.texts?.essential?.title || 'Essential Cookies')
25
25
  : (config.texts?.gdpr?.title || 'Cookie Consent'))
26
26
  }}
27
- </h3>
28
- <!-- Message -->
27
+ </div>
28
+ <!-- Message -->
29
29
  <div v-if="!isSettingsMode && !isEssentialBanner" class="cookie-drawer__message">
30
- {{ config.texts?.gdpr?.message || 'We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.' }}
30
+ {{ config.texts?.gdpr?.message || 'We use cookies to enhance your experience.' }}
31
31
  </div>
32
32
 
33
33
  <button
@@ -36,9 +36,9 @@
36
36
  class="cookie-drawer__close"
37
37
  aria-label="Close"
38
38
  >
39
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
40
- <path d="M6.40013 18.3079L5.69238 17.6001L11.2924 12.0001L5.69238 6.40013L6.40013 5.69238L12.0001 11.2924L17.6001 5.69238L18.3079 6.40013L12.7079 12.0001L18.3079 17.6001L17.6001 18.3079L12.0001 12.7079L6.40013 18.3079Z" fill="#0D0B3D"/>
41
- </svg>
39
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
40
+ <path d="M6.40013 18.3079L5.69238 17.6001L11.2924 12.0001L5.69238 6.40013L6.40013 5.69238L12.0001 11.2924L17.6001 5.69238L18.3079 6.40013L12.7079 12.0001L18.3079 17.6001L17.6001 18.3079L12.0001 12.7079L6.40013 18.3079Z" fill="currentColor"/>
41
+ </svg>
42
42
  </button>
43
43
  </div>
44
44
 
@@ -46,101 +46,191 @@
46
46
  <div v-if="!isSettingsMode && isEssentialBanner" class="cookie-drawer__banner">
47
47
  <div class="cookie-drawer__essential">
48
48
  <div class="cookie-drawer__message cookie-drawer__message--essential">
49
- {{ config.texts?.essential?.message || 'We use essential cookies to make our site work. By using our site, you accept our use of essential cookies.' }}
50
- </div>
51
- <div class="cookie-drawer__actions">
52
- <button @click="$emit('acceptAll')" class="cookie-drawer__button cookie-drawer__button--primary">
53
- {{ config.texts?.essential?.accept || 'Accept' }}
54
- </button>
49
+ {{ config.texts?.essential?.message || 'This site uses only essential cookies.' }}
55
50
  </div>
56
51
  </div>
57
52
  </div>
58
53
 
59
- <!-- GDPR Banner/Settings -->
60
- <div v-else class="cookie-drawer__body">
61
- <!-- Banner Mode -->
62
- <div v-if="!isSettingsMode" class="cookie-drawer__banner">
63
- <div class="cookie-drawer__message">
64
- {{ config.texts?.gdpr?.message || 'We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.' }}
65
- </div>
66
- <div class="cookie-drawer__actions">
67
- <button @click="$emit('rejectAll')" class="cookie-drawer__button cookie-drawer__button--secondary">
68
- {{ config.texts?.gdpr?.reject || 'Reject All' }}
69
- </button>
70
- <button @click="$emit('acceptAll')" class="cookie-drawer__button cookie-drawer__button--primary">
71
- {{ config.texts?.gdpr?.accept || 'Accept All' }}
72
- </button>
73
- <button @click="$emit('openSettings')" class="cookie-drawer__button cookie-drawer__button--link">
74
- {{ config.texts?.gdpr?.settings || 'Customize' }}
75
- </button>
54
+ <!-- GDPR Banner -->
55
+ <div v-else-if="!isSettingsMode" class="cookie-drawer__banner">
56
+ <div class="cookie-drawer__gdpr">
57
+ <!-- Categories -->
58
+ <div class="cookie-drawer__categories">
59
+ <div
60
+ v-for="category in enabledCategories"
61
+ :key="category.id"
62
+ class="cookie-drawer__category"
63
+ >
64
+ <div class="cookie-drawer__category-header">
65
+ <span class="cookie-drawer__category-name">
66
+ {{ category.label }}
67
+ </span>
68
+ <div class="cookie-drawer__toggle">
69
+ <input
70
+ type="checkbox"
71
+ :id="`drawer-category-${category.id}`"
72
+ :checked="categories[category.id]"
73
+ @change="toggleCategory(category.id)"
74
+ :disabled="category.locked"
75
+ class="cookie-drawer__toggle-input"
76
+ />
77
+ <label
78
+ :for="`drawer-category-${category.id}`"
79
+ :class="[
80
+ 'cookie-drawer__toggle-label',
81
+ { 'cookie-drawer__toggle-label--disabled': category.locked }
82
+ ]"
83
+ ></label>
84
+ </div>
85
+ </div>
86
+ <div v-if="category.descriptionShow" class="cookie-drawer__category-description">
87
+ {{ category.description }}
88
+ </div>
89
+ </div>
76
90
  </div>
77
- </div>
78
91
 
79
- <!-- Settings Mode -->
80
- <div v-else class="cookie-drawer__settings">
81
- <!-- Tabs -->
82
- <div v-if="enabledSettingsTabs.length > 1" class="cookie-drawer__tabs">
92
+ <!-- Settings Link -->
93
+ <div class="cookie-drawer__settings-link">
83
94
  <button
84
- v-for="tab in enabledSettingsTabs"
85
- :key="tab"
86
- :class="['cookie-drawer__tab', { 'cookie-drawer__tab--active': currentTab === tab }]"
87
- @click="$emit('selectTab', tab)"
95
+ @click="openSettings"
96
+ class="cookie-drawer__settings-button"
88
97
  >
89
- {{ getCategoryTitle(tab) }}
98
+ {{ config.texts?.buttons?.settings || config.texts?.gdpr?.buttons?.settings || 'Settings' }}
90
99
  </button>
91
100
  </div>
92
101
 
93
- <!-- Tab Content -->
94
- <div class="cookie-drawer__tab-content">
95
- <div v-if="currentTab === 'privacy'" class="cookie-drawer__privacy">
96
- <h4>{{ config.texts?.settings?.privacy?.title || 'Privacy Policy' }}</h4>
97
- <p>{{ config.texts?.settings?.privacy?.description || 'Learn about how we use cookies and protect your privacy.' }}</p>
98
- </div>
102
+ <!-- Capabilities -->
103
+ <div v-if="isV2" class="cookie-drawer__capabilities" aria-live="polite">
104
+ <span v-if="capabilities.gdpr" class="cookie-drawer__capability">GDPR</span>
105
+ <span v-if="capabilities.ccpa" class="cookie-drawer__capability">CCPA</span>
106
+ <span v-if="capabilities.pipeda" class="cookie-drawer__capability">PIPEDA</span>
107
+ <span v-if="capabilities.iabTcf22" class="cookie-drawer__capability">IAB TCF v2.2</span>
108
+ <span v-if="capabilities.googleConsentModeV2" class="cookie-drawer__capability">Google Consent Mode v2</span>
109
+ <span v-if="capabilities.scriptBlocking" class="cookie-drawer__capability">Script Blocking</span>
110
+ </div>
111
+ </div>
112
+ </div>
99
113
 
100
- <div v-else class="cookie-drawer__category">
101
- <div class="cookie-drawer__category-header">
102
- <h4>{{ getCategoryTitle(currentTab) }}</h4>
103
- <label class="cookie-drawer__toggle">
104
- <input
105
- type="checkbox"
106
- :checked="categories[currentTab]?.enabled"
107
- :disabled="categories[currentTab]?.required"
108
- @change="$emit('toggleCategory', currentTab)"
109
- />
110
- <span class="cookie-drawer__toggle-slider"></span>
111
- </label>
112
- </div>
113
- <div class="cookie-drawer__category-description">
114
- {{ getCategoryDescription(currentTab) }}
114
+ <!-- Settings Mode -->
115
+ <div v-else class="cookie-drawer__settings">
116
+ <!-- Tabs -->
117
+ <div class="cookie-drawer__tabs">
118
+ <button
119
+ v-for="tab in availableTabs"
120
+ :key="tab.id"
121
+ @click="selectTab(tab.id)"
122
+ :class="[
123
+ 'cookie-drawer__tab',
124
+ { 'cookie-drawer__tab--active': currentTab === tab.id }
125
+ ]"
126
+ >
127
+ {{ tab.label }}
128
+ </button>
129
+ </div>
130
+
131
+ <!-- Tab Content -->
132
+ <div class="cookie-drawer__tab-content">
133
+ <!-- Privacy Tab -->
134
+ <div v-if="currentTab === 'privacy'" class="cookie-drawer__privacy">
135
+ <div class="cookie-drawer__privacy-content">
136
+ <div class="cookie-drawer__text">
137
+ {{ config.texts?.settings?.privacy?.intro || 'We use cookies and similar technologies to ensure the site works properly.' }}
115
138
  </div>
139
+ <a :href="policyLinks.privacy || '#'" class="cookie-drawer__link">
140
+ {{ config.texts?.settings?.links?.moreInfo || 'More information' }}
141
+ </a>
116
142
  </div>
117
143
  </div>
118
144
 
119
- <!-- Settings Actions -->
120
- <div class="cookie-drawer__settings-actions">
121
- <button @click="$emit('acceptSelection')" class="cookie-drawer__button cookie-drawer__button--primary">
122
- {{ config.texts?.settings?.save || 'Save Preferences' }}
123
- </button>
145
+ <!-- Category Tabs -->
146
+ <div v-else class="cookie-drawer__category">
147
+ <div class="cookie-drawer__category-header">
148
+ <div class="cookie-drawer__category-title">
149
+ {{ currentCategoryData.label }}
150
+ </div>
151
+ <div class="cookie-drawer__toggle">
152
+ <input
153
+ type="checkbox"
154
+ :id="`settings-category-${currentTab}`"
155
+ :checked="categories[currentTab]"
156
+ @change="toggleCategory(currentTab)"
157
+ :disabled="currentCategoryData.locked"
158
+ class="cookie-drawer__toggle-input"
159
+ />
160
+ <label
161
+ :for="`settings-category-${currentTab}`"
162
+ :class="[
163
+ 'cookie-drawer__toggle-label',
164
+ { 'cookie-drawer__toggle-label--disabled': currentCategoryData.locked }
165
+ ]"
166
+ ></label>
167
+ </div>
168
+ </div>
169
+ <div class="cookie-drawer__category-description">
170
+ {{ currentCategoryData.description }}
171
+ </div>
172
+ <a :href="policyLinks.cookies || '#'" class="cookie-drawer__link">
173
+ {{ config.texts?.settings?.links?.cookieDetails || 'Cookie details' }}
174
+ </a>
124
175
  </div>
125
176
  </div>
126
177
  </div>
178
+
179
+ <!-- Actions: GDPR mode -->
180
+ <div v-if="!isEssentialBanner" class="cookie-drawer__actions">
181
+ <button
182
+ @click="handleAcceptSelection"
183
+ class="cookie-drawer__button cookie-drawer__button--outline"
184
+ >
185
+ {{ config.texts?.buttons?.acceptSelection || config.texts?.gdpr?.buttons?.acceptSelection || 'Accept Selection' }}
186
+ </button>
187
+ <button
188
+ @click="handleRejectAll"
189
+ class="cookie-drawer__button cookie-drawer__button--secondary"
190
+ >
191
+ {{ config.texts?.buttons?.rejectAll || config.texts?.gdpr?.buttons?.rejectAll || 'Reject All' }}
192
+ </button>
193
+ <button
194
+ @click="handleAcceptAll"
195
+ class="cookie-drawer__button cookie-drawer__button--primary"
196
+ >
197
+ {{ config.texts?.buttons?.acceptAll || config.texts?.gdpr?.buttons?.acceptAll || 'Accept All' }}
198
+ </button>
199
+ </div>
200
+ <!-- Actions: Essential mode -->
201
+ <div v-else class="cookie-drawer__actions cookie-drawer__actions_essential">
202
+ <button
203
+ @click="handleAcceptAll"
204
+ class="cookie-drawer__button cookie-drawer__button--primary cookie-drawer__button--essential"
205
+ >
206
+ {{ config.texts?.essential?.button || 'Accept' }}
207
+ </button>
208
+ </div>
209
+
127
210
  </div>
128
211
  </div>
129
212
  </Transition>
130
213
  </template>
131
214
 
132
215
  <script setup>
133
- import { computed, ref } from 'vue'
216
+ import { computed, ref, watch, onMounted, onUnmounted } from 'vue'
134
217
 
135
218
  const props = defineProps({
136
- isVisible: Boolean,
137
- isSettingsMode: Boolean,
138
- currentTab: String,
139
- categories: Object,
140
- config: Object,
141
- consentVersion: String,
142
- capabilities: Object,
143
- isV2: Boolean
219
+ isVisible: { type: Boolean, required: true },
220
+ isSettingsMode: { type: Boolean, default: false },
221
+ currentTab: { type: String, required: true },
222
+ categories: { type: Object, required: true },
223
+ config: { type: Object, required: true },
224
+ consentVersion: { type: String, default: 'v1' },
225
+ capabilities: { type: Object, default: () => ({}) },
226
+ isV2: { type: Boolean, default: false }
227
+ })
228
+
229
+ const dialogRef = ref(null)
230
+ const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1024)
231
+
232
+ defineExpose({
233
+ dialogElement: () => dialogRef.value
144
234
  })
145
235
 
146
236
  const emit = defineEmits([
@@ -153,51 +243,131 @@ const emit = defineEmits([
153
243
  'rejectAll'
154
244
  ])
155
245
 
156
- const dialogRef = ref(null)
246
+ // Computed properties
247
+ const enabledCategories = computed(() => {
248
+ if (!props.config.categories) return []
249
+ return Object.keys(props.config.categories)
250
+ .filter(catId => props.config.categories[catId].enabled)
251
+ .map(catId => props.config.categories[catId])
252
+ })
157
253
 
158
- const isEssentialBanner = computed(() => props.config.mode === 'essential')
254
+ const availableTabs = computed(() => {
255
+ const tabs = [
256
+ { id: 'privacy', label: props.config.texts?.settings?.tabs?.privacy || 'Privacy' }
257
+ ]
159
258
 
160
- const drawerClasses = computed(() => ({
161
- 'cookie-drawer__content-wrapper--settings': props.isSettingsMode,
162
- 'cookie-drawer__content-wrapper--banner': !props.isSettingsMode
163
- }))
259
+ if (props.config.categories) {
260
+ Object.keys(props.config.categories).forEach(categoryId => {
261
+ if (props.config.categories[categoryId].enabled) {
262
+ tabs.push({
263
+ id: categoryId,
264
+ label: props.config.categories[categoryId].label
265
+ })
266
+ }
267
+ })
268
+ }
164
269
 
165
- const rootClasses = computed(() => ({
166
- 'cookie-drawer__content-wrapper--bottom': true
167
- }))
270
+ return tabs
271
+ })
168
272
 
169
- const drawerAnimation = computed(() =>
170
- props.isSettingsMode ? 'drawer-slide-up' : 'banner-slide-up'
171
- )
273
+ const currentCategoryData = computed(() => {
274
+ return props.config.categories?.[props.currentTab] || {}
275
+ })
172
276
 
173
- const enabledSettingsTabs = computed(() => {
174
- return ['privacy', ...Object.keys(props.categories || {}).filter(id =>
175
- props.categories[id]?.enabled
176
- )]
277
+ const policyLinks = computed(() => {
278
+ return props.config.policies || {}
177
279
  })
178
280
 
179
- const getCategoryTitle = (categoryId) => {
180
- return props.categories[categoryId]?.name || categoryId
181
- }
281
+ const isEssentialBanner = computed(() => {
282
+ return props.config.mode === 'essential' && !props.isSettingsMode
283
+ })
182
284
 
183
- const getCategoryDescription = (categoryId) => {
184
- return props.categories[categoryId]?.description || `Description for ${categoryId} cookies.`
185
- }
285
+ // Reactive mobile detection
286
+ const isMobile = computed(() => {
287
+ return windowWidth.value <= 767
288
+ })
186
289
 
187
- const handleBackdropClick = () => {
188
- if (!props.isSettingsMode) {
189
- emit('close')
290
+ const handleResize = () => {
291
+ if (typeof window !== 'undefined') {
292
+ windowWidth.value = window.innerWidth
190
293
  }
191
294
  }
192
295
 
296
+ onMounted(() => {
297
+ if (typeof window !== 'undefined') {
298
+ window.addEventListener('resize', handleResize)
299
+ }
300
+ })
301
+
302
+ onUnmounted(() => {
303
+ if (typeof window !== 'undefined') {
304
+ window.removeEventListener('resize', handleResize)
305
+ }
306
+ })
307
+
308
+ // Root classes for essential mode
309
+ const rootClasses = computed(() => {
310
+ return {
311
+ 'cookie-drawer--essential-mode': isEssentialBanner.value
312
+ }
313
+ })
314
+
315
+ // Drawer classes and animations
316
+ const drawerClasses = computed(() => {
317
+ return {
318
+ 'cookie-drawer--mobile': isMobile.value && !isEssentialBanner.value
319
+ }
320
+ })
321
+
322
+ const drawerAnimation = computed(() => {
323
+ if (isEssentialBanner.value) return 'drawer-slide-up'
324
+ if (isMobile.value) return 'drawer-slide-up'
325
+ return 'drawer-slide-right'
326
+ })
327
+
328
+ // Event handlers
193
329
  const handleClose = () => {
194
330
  emit('close')
195
331
  }
196
332
 
197
- // Expose dialog element for focus trap
198
- defineExpose({
199
- dialogElement: () => dialogRef.value
200
- })
333
+ const handleBackdropClick = () => {
334
+ emit('close')
335
+ }
336
+
337
+ const openSettings = () => {
338
+ emit('openSettings')
339
+ }
340
+
341
+ const selectTab = (tabId) => {
342
+ emit('selectTab', tabId)
343
+ }
344
+
345
+ const toggleCategory = (categoryId) => {
346
+ emit('toggleCategory', categoryId)
347
+ }
348
+
349
+ const handleAcceptSelection = () => {
350
+ emit('acceptSelection')
351
+ }
352
+
353
+ const handleAcceptAll = () => {
354
+ emit('acceptAll')
355
+ }
356
+
357
+ const handleRejectAll = () => {
358
+ emit('rejectAll')
359
+ }
360
+
361
+ // Watch for drawer visibility to control body overflow
362
+ watch(
363
+ () => props.isVisible,
364
+ (newVal) => {
365
+ if (isEssentialBanner.value) return
366
+ if (typeof document === 'undefined') return
367
+ document.body.style.overflow = newVal ? 'hidden' : ''
368
+ },
369
+ { immediate: true }
370
+ )
201
371
  </script>
202
372
 
203
373
  <style src="@el7ven/cookie-kit/dist/styles"></style>
package/src/vue/index.js CHANGED
@@ -2,8 +2,12 @@ import { computed, ref } from 'vue'
2
2
  import { createCookieKit, DEFAULT_CONFIG } from '../core/index.js'
3
3
  import { VERSION, getVersion, getVersionInfo, logVersion } from './version.js'
4
4
  import { useCookieConsent } from './composables/useCookieConsent.js'
5
+ import { createAnalyticsManager } from '../core/analytics.js'
5
6
 
6
- export { createCookieKit, DEFAULT_CONFIG, VERSION, getVersion, getVersionInfo, logVersion, useCookieConsent }
7
+ export { createCookieKit, DEFAULT_CONFIG, VERSION, getVersion, getVersionInfo, logVersion, useCookieConsent, createAnalyticsManager }
8
+ export { default as CookieConsent } from './CookieConsent.vue'
9
+ export { default as CookieDrawer } from './CookieDrawer.vue'
10
+ export { default as CookieConsentDebug } from './CookieConsentDebug.vue'
7
11
 
8
12
  export function useCookieKitVue(userConfig = {}) {
9
13
  const core = createCookieKit(userConfig)