@structured-world/vue-privacy 1.2.2 → 1.2.3

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ declare const BANNER_CSS = "\n.consent-banner {\n position: fixed;\n left: 0;\n right: 0;\n z-index: 9999;\n padding: 1rem;\n background: var(--consent-bg, #ffffff);\n color: var(--consent-text, #1a1a1a);\n box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);\n font-family: var(--consent-font, system-ui, -apple-system, sans-serif);\n}\n\n.consent-banner--bottom {\n bottom: 0;\n}\n\n.consent-banner--top {\n top: 0;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.consent-banner--center {\n top: 50%;\n left: 50%;\n right: auto;\n transform: translate(-50%, -50%);\n max-width: 500px;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n}\n\n.consent-banner__content {\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.consent-banner__title {\n margin: 0 0 0.5rem;\n font-size: 1.125rem;\n font-weight: 600;\n}\n\n.consent-banner__message {\n margin: 0 0 1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--consent-text-secondary, #666666);\n}\n\n.consent-banner__privacy-link {\n color: var(--consent-link, #0066cc);\n text-decoration: underline;\n}\n\n.consent-banner__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n justify-content: flex-end;\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.consent-banner__btn {\n padding: 0.5rem 1rem;\n font-size: 0.875rem;\n font-weight: 500;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition:\n background-color 0.2s,\n opacity 0.2s;\n}\n\n.consent-banner__btn:hover {\n opacity: 0.9;\n}\n\n.consent-banner__btn--accept {\n background: var(--consent-btn-accept-bg, #0066cc);\n color: var(--consent-btn-accept-text, #ffffff);\n}\n\n.consent-banner__btn--reject {\n background: var(--consent-btn-reject-bg, #e0e0e0);\n color: var(--consent-btn-reject-text, #1a1a1a);\n}\n\n.consent-banner__btn--customize {\n background: transparent;\n color: var(--consent-link, #0066cc);\n border: 1px solid currentColor;\n}\n\n/* Transitions */\n.consent-banner-enter-active,\n.consent-banner-leave-active {\n transition:\n transform 0.3s ease,\n opacity 0.3s ease;\n}\n\n.consent-banner--bottom.consent-banner-enter-from,\n.consent-banner--bottom.consent-banner-leave-to {\n transform: translateY(100%);\n opacity: 0;\n}\n\n.consent-banner--top.consent-banner-enter-from,\n.consent-banner--top.consent-banner-leave-to {\n transform: translateY(-100%);\n opacity: 0;\n}\n\n.consent-banner--center.consent-banner-enter-from,\n.consent-banner--center.consent-banner-leave-to {\n transform: translate(-50%, -50%) scale(0.9);\n opacity: 0;\n}\n\n/* Dark mode support */\n@media (prefers-color-scheme: dark) {\n .consent-banner {\n --consent-bg: #1a1a1a;\n --consent-text: #ffffff;\n --consent-text-secondary: #a0a0a0;\n --consent-btn-reject-bg: #333333;\n --consent-btn-reject-text: #ffffff;\n }\n}\n\n/* Mobile responsiveness */\n@media (max-width: 640px) {\n .consent-banner__actions {\n flex-direction: column;\n }\n\n .consent-banner__btn {\n width: 100%;\n justify-content: center;\n }\n}\n";
2
+ /** Raw CSS string for consumers who need to include styles via their own build pipeline (e.g. strict CSP) */
3
+ export { BANNER_CSS as consentBannerCSS };
4
+ /** Inject ConsentBanner CSS into document head (idempotent, SSR-safe) */
5
+ export declare function injectBannerStyles(): void;
@@ -64,4 +64,5 @@ export declare function useConsent(): {
64
64
  manager: ConsentManager;
65
65
  };
66
66
  export { ConsentBanner };
67
+ export { consentBannerCSS } from "./banner-styles";
67
68
  export type { ConsentConfig, ConsentManager };
package/dist/vue/index.js CHANGED
@@ -1,5 +1,159 @@
1
1
  import { DEFAULT_CONFIG, createConsentManager } from "../index.js";
2
2
  import { defineComponent, inject, ref, computed, onMounted, openBlock, createBlock, Teleport, createVNode, Transition, withCtx, createElementBlock, normalizeClass, createElementVNode, toDisplayString, createTextVNode, createCommentVNode } from "vue";
3
+ const BANNER_CSS = `
4
+ .consent-banner {
5
+ position: fixed;
6
+ left: 0;
7
+ right: 0;
8
+ z-index: 9999;
9
+ padding: 1rem;
10
+ background: var(--consent-bg, #ffffff);
11
+ color: var(--consent-text, #1a1a1a);
12
+ box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
13
+ font-family: var(--consent-font, system-ui, -apple-system, sans-serif);
14
+ }
15
+
16
+ .consent-banner--bottom {
17
+ bottom: 0;
18
+ }
19
+
20
+ .consent-banner--top {
21
+ top: 0;
22
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
23
+ }
24
+
25
+ .consent-banner--center {
26
+ top: 50%;
27
+ left: 50%;
28
+ right: auto;
29
+ transform: translate(-50%, -50%);
30
+ max-width: 500px;
31
+ border-radius: 8px;
32
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
33
+ }
34
+
35
+ .consent-banner__content {
36
+ max-width: 1200px;
37
+ margin: 0 auto;
38
+ }
39
+
40
+ .consent-banner__title {
41
+ margin: 0 0 0.5rem;
42
+ font-size: 1.125rem;
43
+ font-weight: 600;
44
+ }
45
+
46
+ .consent-banner__message {
47
+ margin: 0 0 1rem;
48
+ font-size: 0.875rem;
49
+ line-height: 1.5;
50
+ color: var(--consent-text-secondary, #666666);
51
+ }
52
+
53
+ .consent-banner__privacy-link {
54
+ color: var(--consent-link, #0066cc);
55
+ text-decoration: underline;
56
+ }
57
+
58
+ .consent-banner__actions {
59
+ display: flex;
60
+ flex-wrap: wrap;
61
+ gap: 0.5rem;
62
+ justify-content: flex-end;
63
+ max-width: 1200px;
64
+ margin: 0 auto;
65
+ }
66
+
67
+ .consent-banner__btn {
68
+ padding: 0.5rem 1rem;
69
+ font-size: 0.875rem;
70
+ font-weight: 500;
71
+ border: none;
72
+ border-radius: 4px;
73
+ cursor: pointer;
74
+ transition:
75
+ background-color 0.2s,
76
+ opacity 0.2s;
77
+ }
78
+
79
+ .consent-banner__btn:hover {
80
+ opacity: 0.9;
81
+ }
82
+
83
+ .consent-banner__btn--accept {
84
+ background: var(--consent-btn-accept-bg, #0066cc);
85
+ color: var(--consent-btn-accept-text, #ffffff);
86
+ }
87
+
88
+ .consent-banner__btn--reject {
89
+ background: var(--consent-btn-reject-bg, #e0e0e0);
90
+ color: var(--consent-btn-reject-text, #1a1a1a);
91
+ }
92
+
93
+ .consent-banner__btn--customize {
94
+ background: transparent;
95
+ color: var(--consent-link, #0066cc);
96
+ border: 1px solid currentColor;
97
+ }
98
+
99
+ /* Transitions */
100
+ .consent-banner-enter-active,
101
+ .consent-banner-leave-active {
102
+ transition:
103
+ transform 0.3s ease,
104
+ opacity 0.3s ease;
105
+ }
106
+
107
+ .consent-banner--bottom.consent-banner-enter-from,
108
+ .consent-banner--bottom.consent-banner-leave-to {
109
+ transform: translateY(100%);
110
+ opacity: 0;
111
+ }
112
+
113
+ .consent-banner--top.consent-banner-enter-from,
114
+ .consent-banner--top.consent-banner-leave-to {
115
+ transform: translateY(-100%);
116
+ opacity: 0;
117
+ }
118
+
119
+ .consent-banner--center.consent-banner-enter-from,
120
+ .consent-banner--center.consent-banner-leave-to {
121
+ transform: translate(-50%, -50%) scale(0.9);
122
+ opacity: 0;
123
+ }
124
+
125
+ /* Dark mode support */
126
+ @media (prefers-color-scheme: dark) {
127
+ .consent-banner {
128
+ --consent-bg: #1a1a1a;
129
+ --consent-text: #ffffff;
130
+ --consent-text-secondary: #a0a0a0;
131
+ --consent-btn-reject-bg: #333333;
132
+ --consent-btn-reject-text: #ffffff;
133
+ }
134
+ }
135
+
136
+ /* Mobile responsiveness */
137
+ @media (max-width: 640px) {
138
+ .consent-banner__actions {
139
+ flex-direction: column;
140
+ }
141
+
142
+ .consent-banner__btn {
143
+ width: 100%;
144
+ justify-content: center;
145
+ }
146
+ }
147
+ `;
148
+ const STYLE_ID = "vue-privacy-consent-banner";
149
+ function injectBannerStyles() {
150
+ if (typeof document === "undefined") return;
151
+ if (document.getElementById(STYLE_ID)) return;
152
+ const style = document.createElement("style");
153
+ style.id = STYLE_ID;
154
+ style.textContent = BANNER_CSS;
155
+ document.head.appendChild(style);
156
+ }
3
157
  const _hoisted_1 = { class: "consent-banner__content" };
4
158
  const _hoisted_2 = {
5
159
  id: "consent-banner-title",
@@ -46,6 +200,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
46
200
  return "consent-banner--bottom";
47
201
  }
48
202
  });
203
+ injectBannerStyles();
49
204
  onMounted(() => {
50
205
  if (consentManager) {
51
206
  consentManager.onShowBanner(() => {
@@ -173,6 +328,7 @@ function useConsent() {
173
328
  export {
174
329
  CONSENT_MANAGER_KEY,
175
330
  _sfc_main as ConsentBanner,
331
+ BANNER_CSS as consentBannerCSS,
176
332
  createConsentPlugin,
177
333
  useConsent
178
334
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/vue/ConsentBanner.vue","../../src/vue/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, onMounted, inject } from \"vue\";\nimport type { ConsentManager } from \"../core/consent-manager\";\nimport type { BannerConfig, BannerConfigDefaults } from \"../core/types\";\nimport { DEFAULT_CONFIG } from \"../core/types\";\n\nconst props = defineProps<{\n /** Custom banner configuration */\n config?: Partial<BannerConfig>;\n /** Position of the banner */\n position?: \"bottom\" | \"top\" | \"center\";\n}>();\n\nconst emit = defineEmits<{\n accept: [];\n reject: [];\n customize: [];\n}>();\n\n// Inject consent manager from Vue plugin\nconst consentManager = inject<ConsentManager>(\"consentManager\");\n\n// State\nconst visible = ref(false);\n\n// Merged config with defaults\nconst bannerConfig = computed<BannerConfigDefaults>(() => {\n const managerConfig = consentManager?.getConfig().banner;\n const propsConfig = props.config;\n return {\n title: propsConfig?.title ?? managerConfig?.title ?? DEFAULT_CONFIG.banner.title,\n message: propsConfig?.message ?? managerConfig?.message ?? DEFAULT_CONFIG.banner.message,\n acceptAll:\n propsConfig?.acceptAll ?? managerConfig?.acceptAll ?? DEFAULT_CONFIG.banner.acceptAll,\n rejectAll:\n propsConfig?.rejectAll ?? managerConfig?.rejectAll ?? DEFAULT_CONFIG.banner.rejectAll,\n customize:\n propsConfig?.customize ?? managerConfig?.customize ?? DEFAULT_CONFIG.banner.customize,\n privacyLink:\n propsConfig?.privacyLink ?? managerConfig?.privacyLink ?? DEFAULT_CONFIG.banner.privacyLink,\n privacyLinkText:\n propsConfig?.privacyLinkText ??\n managerConfig?.privacyLinkText ??\n DEFAULT_CONFIG.banner.privacyLinkText,\n };\n});\n\n// Position classes\nconst positionClasses = computed(() => {\n switch (props.position ?? \"bottom\") {\n case \"top\":\n return \"consent-banner--top\";\n case \"center\":\n return \"consent-banner--center\";\n default:\n return \"consent-banner--bottom\";\n }\n});\n\n// Register show/hide callbacks with manager\nonMounted(() => {\n if (consentManager) {\n consentManager.onShowBanner(() => {\n visible.value = true;\n });\n\n consentManager.onHideBanner(() => {\n visible.value = false;\n });\n }\n});\n\n// Actions\nasync function handleAccept() {\n await consentManager?.acceptAll();\n emit(\"accept\");\n}\n\nasync function handleReject() {\n await consentManager?.rejectAll();\n emit(\"reject\");\n}\n\nfunction handleCustomize() {\n emit(\"customize\");\n}\n</script>\n\n<template>\n <Teleport to=\"body\">\n <Transition name=\"consent-banner\">\n <div\n v-if=\"visible\"\n class=\"consent-banner\"\n :class=\"positionClasses\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"consent-banner-title\"\n aria-describedby=\"consent-banner-message\"\n >\n <div class=\"consent-banner__content\">\n <h2 id=\"consent-banner-title\" class=\"consent-banner__title\">\n {{ bannerConfig.title }}\n </h2>\n <p id=\"consent-banner-message\" class=\"consent-banner__message\">\n {{ bannerConfig.message }}\n <a\n v-if=\"bannerConfig.privacyLink\"\n :href=\"bannerConfig.privacyLink\"\n class=\"consent-banner__privacy-link\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ bannerConfig.privacyLinkText }}\n </a>\n </p>\n </div>\n\n <div class=\"consent-banner__actions\">\n <button\n type=\"button\"\n class=\"consent-banner__btn consent-banner__btn--reject\"\n @click=\"handleReject\"\n >\n {{ bannerConfig.rejectAll }}\n </button>\n\n <button\n v-if=\"bannerConfig.customize\"\n type=\"button\"\n class=\"consent-banner__btn consent-banner__btn--customize\"\n @click=\"handleCustomize\"\n >\n {{ bannerConfig.customize }}\n </button>\n\n <button\n type=\"button\"\n class=\"consent-banner__btn consent-banner__btn--accept\"\n @click=\"handleAccept\"\n >\n {{ bannerConfig.acceptAll }}\n </button>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style>\n.consent-banner {\n position: fixed;\n left: 0;\n right: 0;\n z-index: 9999;\n padding: 1rem;\n background: var(--consent-bg, #ffffff);\n color: var(--consent-text, #1a1a1a);\n box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);\n font-family: var(--consent-font, system-ui, -apple-system, sans-serif);\n}\n\n.consent-banner--bottom {\n bottom: 0;\n}\n\n.consent-banner--top {\n top: 0;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.consent-banner--center {\n top: 50%;\n left: 50%;\n right: auto;\n transform: translate(-50%, -50%);\n max-width: 500px;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n}\n\n.consent-banner__content {\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.consent-banner__title {\n margin: 0 0 0.5rem;\n font-size: 1.125rem;\n font-weight: 600;\n}\n\n.consent-banner__message {\n margin: 0 0 1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--consent-text-secondary, #666666);\n}\n\n.consent-banner__privacy-link {\n color: var(--consent-link, #0066cc);\n text-decoration: underline;\n}\n\n.consent-banner__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n justify-content: flex-end;\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.consent-banner__btn {\n padding: 0.5rem 1rem;\n font-size: 0.875rem;\n font-weight: 500;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition:\n background-color 0.2s,\n opacity 0.2s;\n}\n\n.consent-banner__btn:hover {\n opacity: 0.9;\n}\n\n.consent-banner__btn--accept {\n background: var(--consent-btn-accept-bg, #0066cc);\n color: var(--consent-btn-accept-text, #ffffff);\n}\n\n.consent-banner__btn--reject {\n background: var(--consent-btn-reject-bg, #e0e0e0);\n color: var(--consent-btn-reject-text, #1a1a1a);\n}\n\n.consent-banner__btn--customize {\n background: transparent;\n color: var(--consent-link, #0066cc);\n border: 1px solid currentColor;\n}\n\n/* Transitions */\n.consent-banner-enter-active,\n.consent-banner-leave-active {\n transition:\n transform 0.3s ease,\n opacity 0.3s ease;\n}\n\n.consent-banner--bottom.consent-banner-enter-from,\n.consent-banner--bottom.consent-banner-leave-to {\n transform: translateY(100%);\n opacity: 0;\n}\n\n.consent-banner--top.consent-banner-enter-from,\n.consent-banner--top.consent-banner-leave-to {\n transform: translateY(-100%);\n opacity: 0;\n}\n\n.consent-banner--center.consent-banner-enter-from,\n.consent-banner--center.consent-banner-leave-to {\n transform: translate(-50%, -50%) scale(0.9);\n opacity: 0;\n}\n\n/* Dark mode support */\n@media (prefers-color-scheme: dark) {\n .consent-banner {\n --consent-bg: #1a1a1a;\n --consent-text: #ffffff;\n --consent-text-secondary: #a0a0a0;\n --consent-btn-reject-bg: #333333;\n --consent-btn-reject-text: #ffffff;\n }\n}\n\n/* Mobile responsiveness */\n@media (max-width: 640px) {\n .consent-banner__actions {\n flex-direction: column;\n }\n\n .consent-banner__btn {\n width: 100%;\n justify-content: center;\n }\n}\n</style>\n","import type { App, Plugin } from \"vue\";\nimport type { ConsentConfig } from \"../core/types\";\nimport { ConsentManager, createConsentManager } from \"../core/consent-manager\";\nimport ConsentBanner from \"./ConsentBanner.vue\";\n\n/**\n * Vue plugin options\n */\nexport interface ConsentPluginOptions extends ConsentConfig {\n /** Auto-initialize on plugin install */\n autoInit?: boolean;\n}\n\n/**\n * Symbol for injection\n */\nexport const CONSENT_MANAGER_KEY = Symbol(\"consentManager\");\n\n/**\n * Create Vue plugin for cookie consent\n *\n * @example\n * ```ts\n * import { createApp } from 'vue';\n * import { createConsentPlugin } from '@structured-world/vue-privacy/vue';\n *\n * const app = createApp(App);\n * app.use(createConsentPlugin({\n * gaId: 'G-XXXXXXXXXX',\n * autoInit: true,\n * }));\n * ```\n */\nexport function createConsentPlugin(options: ConsentPluginOptions = {}): Plugin {\n const { autoInit = true, ...config } = options;\n\n return {\n install(app: App) {\n const manager = createConsentManager(config);\n\n // Provide manager for injection\n app.provide(\"consentManager\", manager);\n app.provide(CONSENT_MANAGER_KEY, manager);\n\n // Register global component\n app.component(\"ConsentBanner\", ConsentBanner);\n\n // Auto-initialize if requested\n if (autoInit) {\n // Wait for app to mount, then initialize\n const originalMount = app.mount.bind(app);\n app.mount = (rootContainer, ...args) => {\n const result = originalMount(rootContainer, ...args);\n\n // Initialize after mount\n manager.init().catch((err) => {\n console.error(\"[@structured-world/vue-privacy] Failed to initialize:\", err);\n });\n\n return result;\n };\n }\n },\n };\n}\n\n/**\n * Composable to access consent manager\n *\n * @example\n * ```vue\n * <script setup>\n * import { useConsent } from '@structured-world/vue-privacy/vue';\n *\n * const { acceptAll, rejectAll, hasConsent } = useConsent();\n * </script>\n * ```\n */\nexport function useConsent() {\n const manager = inject<ConsentManager>(\"consentManager\");\n\n if (!manager) {\n throw new Error(\n \"[@structured-world/vue-privacy] useConsent() called without plugin. \" +\n \"Did you forget to app.use(createConsentPlugin())?\"\n );\n }\n\n return {\n /** Accept all cookies */\n acceptAll: () => manager.acceptAll(),\n /** Reject all non-essential cookies */\n rejectAll: () => manager.rejectAll(),\n /** Save custom preferences */\n savePreferences: (categories: Parameters<typeof manager.savePreferences>[0]) =>\n manager.savePreferences(categories),\n /** Get current consent state */\n getConsent: () => manager.getConsent(),\n /** Check if user has made a consent choice */\n hasConsent: () => manager.hasConsent(),\n /** Reset consent and show banner again */\n resetConsent: () => manager.resetConsent(),\n /** Track a page view manually (for SPA navigation) */\n trackPageView: (path: string, title?: string) => manager.trackPageView(path, title),\n /** Check if user is detected as EU */\n isEUUser: () => manager.isEUUser(),\n /** Get geo-detection result (country, method, isEU) */\n getGeoResult: () => manager.getGeoResult(),\n /** Get the underlying manager instance */\n manager,\n };\n}\n\n// Need to import inject for useConsent\nimport { inject } from \"vue\";\n\n// Re-export component\nexport { ConsentBanner };\n\n// Re-export types\nexport type { ConsentConfig, ConsentManager };\n"],"names":["_createBlock","_Teleport","_createVNode","_Transition","_createElementBlock","_normalizeClass","_createElementVNode","_toDisplayString","ConsentBanner"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/vue/banner-styles.ts","../../src/vue/ConsentBanner.vue","../../src/vue/index.ts"],"sourcesContent":["const BANNER_CSS = `\n.consent-banner {\n position: fixed;\n left: 0;\n right: 0;\n z-index: 9999;\n padding: 1rem;\n background: var(--consent-bg, #ffffff);\n color: var(--consent-text, #1a1a1a);\n box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);\n font-family: var(--consent-font, system-ui, -apple-system, sans-serif);\n}\n\n.consent-banner--bottom {\n bottom: 0;\n}\n\n.consent-banner--top {\n top: 0;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.consent-banner--center {\n top: 50%;\n left: 50%;\n right: auto;\n transform: translate(-50%, -50%);\n max-width: 500px;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n}\n\n.consent-banner__content {\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.consent-banner__title {\n margin: 0 0 0.5rem;\n font-size: 1.125rem;\n font-weight: 600;\n}\n\n.consent-banner__message {\n margin: 0 0 1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--consent-text-secondary, #666666);\n}\n\n.consent-banner__privacy-link {\n color: var(--consent-link, #0066cc);\n text-decoration: underline;\n}\n\n.consent-banner__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n justify-content: flex-end;\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.consent-banner__btn {\n padding: 0.5rem 1rem;\n font-size: 0.875rem;\n font-weight: 500;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition:\n background-color 0.2s,\n opacity 0.2s;\n}\n\n.consent-banner__btn:hover {\n opacity: 0.9;\n}\n\n.consent-banner__btn--accept {\n background: var(--consent-btn-accept-bg, #0066cc);\n color: var(--consent-btn-accept-text, #ffffff);\n}\n\n.consent-banner__btn--reject {\n background: var(--consent-btn-reject-bg, #e0e0e0);\n color: var(--consent-btn-reject-text, #1a1a1a);\n}\n\n.consent-banner__btn--customize {\n background: transparent;\n color: var(--consent-link, #0066cc);\n border: 1px solid currentColor;\n}\n\n/* Transitions */\n.consent-banner-enter-active,\n.consent-banner-leave-active {\n transition:\n transform 0.3s ease,\n opacity 0.3s ease;\n}\n\n.consent-banner--bottom.consent-banner-enter-from,\n.consent-banner--bottom.consent-banner-leave-to {\n transform: translateY(100%);\n opacity: 0;\n}\n\n.consent-banner--top.consent-banner-enter-from,\n.consent-banner--top.consent-banner-leave-to {\n transform: translateY(-100%);\n opacity: 0;\n}\n\n.consent-banner--center.consent-banner-enter-from,\n.consent-banner--center.consent-banner-leave-to {\n transform: translate(-50%, -50%) scale(0.9);\n opacity: 0;\n}\n\n/* Dark mode support */\n@media (prefers-color-scheme: dark) {\n .consent-banner {\n --consent-bg: #1a1a1a;\n --consent-text: #ffffff;\n --consent-text-secondary: #a0a0a0;\n --consent-btn-reject-bg: #333333;\n --consent-btn-reject-text: #ffffff;\n }\n}\n\n/* Mobile responsiveness */\n@media (max-width: 640px) {\n .consent-banner__actions {\n flex-direction: column;\n }\n\n .consent-banner__btn {\n width: 100%;\n justify-content: center;\n }\n}\n`;\n\nconst STYLE_ID = \"vue-privacy-consent-banner\";\n\n/** Raw CSS string for consumers who need to include styles via their own build pipeline (e.g. strict CSP) */\nexport { BANNER_CSS as consentBannerCSS };\n\n/** Inject ConsentBanner CSS into document head (idempotent, SSR-safe) */\nexport function injectBannerStyles(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(STYLE_ID)) return;\n\n // Strict CSP without 'unsafe-inline' will block this injection.\n // For such environments, use the exported `consentBannerCSS` string\n // to include styles in your own stylesheet or CSP-compliant pipeline.\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = BANNER_CSS;\n document.head.appendChild(style);\n}\n","<script setup lang=\"ts\">\nimport { ref, computed, onMounted, inject } from \"vue\";\nimport type { ConsentManager } from \"../core/consent-manager\";\nimport type { BannerConfig, BannerConfigDefaults } from \"../core/types\";\nimport { DEFAULT_CONFIG } from \"../core/types\";\nimport { injectBannerStyles } from \"./banner-styles\";\n\nconst props = defineProps<{\n /** Custom banner configuration */\n config?: Partial<BannerConfig>;\n /** Position of the banner */\n position?: \"bottom\" | \"top\" | \"center\";\n}>();\n\nconst emit = defineEmits<{\n accept: [];\n reject: [];\n customize: [];\n}>();\n\n// Inject consent manager from Vue plugin\nconst consentManager = inject<ConsentManager>(\"consentManager\");\n\n// State\nconst visible = ref(false);\n\n// Merged config with defaults\nconst bannerConfig = computed<BannerConfigDefaults>(() => {\n const managerConfig = consentManager?.getConfig().banner;\n const propsConfig = props.config;\n return {\n title: propsConfig?.title ?? managerConfig?.title ?? DEFAULT_CONFIG.banner.title,\n message: propsConfig?.message ?? managerConfig?.message ?? DEFAULT_CONFIG.banner.message,\n acceptAll:\n propsConfig?.acceptAll ?? managerConfig?.acceptAll ?? DEFAULT_CONFIG.banner.acceptAll,\n rejectAll:\n propsConfig?.rejectAll ?? managerConfig?.rejectAll ?? DEFAULT_CONFIG.banner.rejectAll,\n customize:\n propsConfig?.customize ?? managerConfig?.customize ?? DEFAULT_CONFIG.banner.customize,\n privacyLink:\n propsConfig?.privacyLink ?? managerConfig?.privacyLink ?? DEFAULT_CONFIG.banner.privacyLink,\n privacyLinkText:\n propsConfig?.privacyLinkText ??\n managerConfig?.privacyLinkText ??\n DEFAULT_CONFIG.banner.privacyLinkText,\n };\n});\n\n// Position classes\nconst positionClasses = computed(() => {\n switch (props.position ?? \"bottom\") {\n case \"top\":\n return \"consent-banner--top\";\n case \"center\":\n return \"consent-banner--center\";\n default:\n return \"consent-banner--bottom\";\n }\n});\n\n// Inject CSS before first render to avoid FOUC (SSR-safe: guard inside injectBannerStyles)\ninjectBannerStyles();\n\n// Register show/hide callbacks with manager\nonMounted(() => {\n if (consentManager) {\n consentManager.onShowBanner(() => {\n visible.value = true;\n });\n\n consentManager.onHideBanner(() => {\n visible.value = false;\n });\n }\n});\n\n// Actions\nasync function handleAccept() {\n await consentManager?.acceptAll();\n emit(\"accept\");\n}\n\nasync function handleReject() {\n await consentManager?.rejectAll();\n emit(\"reject\");\n}\n\nfunction handleCustomize() {\n emit(\"customize\");\n}\n</script>\n\n<template>\n <Teleport to=\"body\">\n <Transition name=\"consent-banner\">\n <div\n v-if=\"visible\"\n class=\"consent-banner\"\n :class=\"positionClasses\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"consent-banner-title\"\n aria-describedby=\"consent-banner-message\"\n >\n <div class=\"consent-banner__content\">\n <h2 id=\"consent-banner-title\" class=\"consent-banner__title\">\n {{ bannerConfig.title }}\n </h2>\n <p id=\"consent-banner-message\" class=\"consent-banner__message\">\n {{ bannerConfig.message }}\n <a\n v-if=\"bannerConfig.privacyLink\"\n :href=\"bannerConfig.privacyLink\"\n class=\"consent-banner__privacy-link\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ bannerConfig.privacyLinkText }}\n </a>\n </p>\n </div>\n\n <div class=\"consent-banner__actions\">\n <button\n type=\"button\"\n class=\"consent-banner__btn consent-banner__btn--reject\"\n @click=\"handleReject\"\n >\n {{ bannerConfig.rejectAll }}\n </button>\n\n <button\n v-if=\"bannerConfig.customize\"\n type=\"button\"\n class=\"consent-banner__btn consent-banner__btn--customize\"\n @click=\"handleCustomize\"\n >\n {{ bannerConfig.customize }}\n </button>\n\n <button\n type=\"button\"\n class=\"consent-banner__btn consent-banner__btn--accept\"\n @click=\"handleAccept\"\n >\n {{ bannerConfig.acceptAll }}\n </button>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","import type { App, Plugin } from \"vue\";\nimport type { ConsentConfig } from \"../core/types\";\nimport { ConsentManager, createConsentManager } from \"../core/consent-manager\";\nimport ConsentBanner from \"./ConsentBanner.vue\";\n\n/**\n * Vue plugin options\n */\nexport interface ConsentPluginOptions extends ConsentConfig {\n /** Auto-initialize on plugin install */\n autoInit?: boolean;\n}\n\n/**\n * Symbol for injection\n */\nexport const CONSENT_MANAGER_KEY = Symbol(\"consentManager\");\n\n/**\n * Create Vue plugin for cookie consent\n *\n * @example\n * ```ts\n * import { createApp } from 'vue';\n * import { createConsentPlugin } from '@structured-world/vue-privacy/vue';\n *\n * const app = createApp(App);\n * app.use(createConsentPlugin({\n * gaId: 'G-XXXXXXXXXX',\n * autoInit: true,\n * }));\n * ```\n */\nexport function createConsentPlugin(options: ConsentPluginOptions = {}): Plugin {\n const { autoInit = true, ...config } = options;\n\n return {\n install(app: App) {\n const manager = createConsentManager(config);\n\n // Provide manager for injection\n app.provide(\"consentManager\", manager);\n app.provide(CONSENT_MANAGER_KEY, manager);\n\n // Register global component\n app.component(\"ConsentBanner\", ConsentBanner);\n\n // Auto-initialize if requested\n if (autoInit) {\n // Wait for app to mount, then initialize\n const originalMount = app.mount.bind(app);\n app.mount = (rootContainer, ...args) => {\n const result = originalMount(rootContainer, ...args);\n\n // Initialize after mount\n manager.init().catch((err) => {\n console.error(\"[@structured-world/vue-privacy] Failed to initialize:\", err);\n });\n\n return result;\n };\n }\n },\n };\n}\n\n/**\n * Composable to access consent manager\n *\n * @example\n * ```vue\n * <script setup>\n * import { useConsent } from '@structured-world/vue-privacy/vue';\n *\n * const { acceptAll, rejectAll, hasConsent } = useConsent();\n * </script>\n * ```\n */\nexport function useConsent() {\n const manager = inject<ConsentManager>(\"consentManager\");\n\n if (!manager) {\n throw new Error(\n \"[@structured-world/vue-privacy] useConsent() called without plugin. \" +\n \"Did you forget to app.use(createConsentPlugin())?\"\n );\n }\n\n return {\n /** Accept all cookies */\n acceptAll: () => manager.acceptAll(),\n /** Reject all non-essential cookies */\n rejectAll: () => manager.rejectAll(),\n /** Save custom preferences */\n savePreferences: (categories: Parameters<typeof manager.savePreferences>[0]) =>\n manager.savePreferences(categories),\n /** Get current consent state */\n getConsent: () => manager.getConsent(),\n /** Check if user has made a consent choice */\n hasConsent: () => manager.hasConsent(),\n /** Reset consent and show banner again */\n resetConsent: () => manager.resetConsent(),\n /** Track a page view manually (for SPA navigation) */\n trackPageView: (path: string, title?: string) => manager.trackPageView(path, title),\n /** Check if user is detected as EU */\n isEUUser: () => manager.isEUUser(),\n /** Get geo-detection result (country, method, isEU) */\n getGeoResult: () => manager.getGeoResult(),\n /** Get the underlying manager instance */\n manager,\n };\n}\n\n// Need to import inject for useConsent\nimport { inject } from \"vue\";\n\n// Re-export component\nexport { ConsentBanner };\n\n// Re-export raw CSS for consumers with strict CSP\nexport { consentBannerCSS } from \"./banner-styles\";\n\n// Re-export types\nexport type { ConsentConfig, ConsentManager };\n"],"names":["_createBlock","_Teleport","_createVNode","_Transition","_createElementBlock","_normalizeClass","_createElementVNode","_toDisplayString","ConsentBanner"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@structured-world/vue-privacy",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Add Google Analytics (GA4) to Vue 3, VitePress, and Quasar with one line of code. Works with Nuxt 3 via the Vue plugin. GDPR/CCPA compliant cookie consent with Google Consent Mode v2, EU auto-detection, and SPA page tracking.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dmitry Prudnikov <mail@polaz.com>",
@@ -22,8 +22,7 @@
22
22
  "./quasar": {
23
23
  "types": "./dist/quasar/index.d.ts",
24
24
  "import": "./dist/quasar/index.js"
25
- },
26
- "./styles": "./dist/styles/consent.css"
25
+ }
27
26
  },
28
27
  "main": "./dist/index.js",
29
28
  "module": "./dist/index.js",
@@ -1,127 +0,0 @@
1
-
2
- .consent-banner {
3
- position: fixed;
4
- left: 0;
5
- right: 0;
6
- z-index: 9999;
7
- padding: 1rem;
8
- background: var(--consent-bg, #ffffff);
9
- color: var(--consent-text, #1a1a1a);
10
- box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
11
- font-family: var(--consent-font, system-ui, -apple-system, sans-serif);
12
- }
13
- .consent-banner--bottom {
14
- bottom: 0;
15
- }
16
- .consent-banner--top {
17
- top: 0;
18
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
19
- }
20
- .consent-banner--center {
21
- top: 50%;
22
- left: 50%;
23
- right: auto;
24
- transform: translate(-50%, -50%);
25
- max-width: 500px;
26
- border-radius: 8px;
27
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
28
- }
29
- .consent-banner__content {
30
- max-width: 1200px;
31
- margin: 0 auto;
32
- }
33
- .consent-banner__title {
34
- margin: 0 0 0.5rem;
35
- font-size: 1.125rem;
36
- font-weight: 600;
37
- }
38
- .consent-banner__message {
39
- margin: 0 0 1rem;
40
- font-size: 0.875rem;
41
- line-height: 1.5;
42
- color: var(--consent-text-secondary, #666666);
43
- }
44
- .consent-banner__privacy-link {
45
- color: var(--consent-link, #0066cc);
46
- text-decoration: underline;
47
- }
48
- .consent-banner__actions {
49
- display: flex;
50
- flex-wrap: wrap;
51
- gap: 0.5rem;
52
- justify-content: flex-end;
53
- max-width: 1200px;
54
- margin: 0 auto;
55
- }
56
- .consent-banner__btn {
57
- padding: 0.5rem 1rem;
58
- font-size: 0.875rem;
59
- font-weight: 500;
60
- border: none;
61
- border-radius: 4px;
62
- cursor: pointer;
63
- transition:
64
- background-color 0.2s,
65
- opacity 0.2s;
66
- }
67
- .consent-banner__btn:hover {
68
- opacity: 0.9;
69
- }
70
- .consent-banner__btn--accept {
71
- background: var(--consent-btn-accept-bg, #0066cc);
72
- color: var(--consent-btn-accept-text, #ffffff);
73
- }
74
- .consent-banner__btn--reject {
75
- background: var(--consent-btn-reject-bg, #e0e0e0);
76
- color: var(--consent-btn-reject-text, #1a1a1a);
77
- }
78
- .consent-banner__btn--customize {
79
- background: transparent;
80
- color: var(--consent-link, #0066cc);
81
- border: 1px solid currentColor;
82
- }
83
-
84
- /* Transitions */
85
- .consent-banner-enter-active,
86
- .consent-banner-leave-active {
87
- transition:
88
- transform 0.3s ease,
89
- opacity 0.3s ease;
90
- }
91
- .consent-banner--bottom.consent-banner-enter-from,
92
- .consent-banner--bottom.consent-banner-leave-to {
93
- transform: translateY(100%);
94
- opacity: 0;
95
- }
96
- .consent-banner--top.consent-banner-enter-from,
97
- .consent-banner--top.consent-banner-leave-to {
98
- transform: translateY(-100%);
99
- opacity: 0;
100
- }
101
- .consent-banner--center.consent-banner-enter-from,
102
- .consent-banner--center.consent-banner-leave-to {
103
- transform: translate(-50%, -50%) scale(0.9);
104
- opacity: 0;
105
- }
106
-
107
- /* Dark mode support */
108
- @media (prefers-color-scheme: dark) {
109
- .consent-banner {
110
- --consent-bg: #1a1a1a;
111
- --consent-text: #ffffff;
112
- --consent-text-secondary: #a0a0a0;
113
- --consent-btn-reject-bg: #333333;
114
- --consent-btn-reject-text: #ffffff;
115
- }
116
- }
117
-
118
- /* Mobile responsiveness */
119
- @media (max-width: 640px) {
120
- .consent-banner__actions {
121
- flex-direction: column;
122
- }
123
- .consent-banner__btn {
124
- width: 100%;
125
- justify-content: center;
126
- }
127
- }