@freshjuice/zest 0.1.0 → 1.0.0

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.
Files changed (76) hide show
  1. package/README.md +46 -0
  2. package/dist/zest.de.js +104 -1
  3. package/dist/zest.de.js.map +1 -1
  4. package/dist/zest.de.min.js +1 -1
  5. package/dist/zest.en.js +104 -1
  6. package/dist/zest.en.js.map +1 -1
  7. package/dist/zest.en.min.js +1 -1
  8. package/dist/zest.es.js +104 -1
  9. package/dist/zest.es.js.map +1 -1
  10. package/dist/zest.es.min.js +1 -1
  11. package/dist/zest.esm.js +104 -1
  12. package/dist/zest.esm.js.map +1 -1
  13. package/dist/zest.esm.min.js +1 -1
  14. package/dist/zest.fr.js +104 -1
  15. package/dist/zest.fr.js.map +1 -1
  16. package/dist/zest.fr.min.js +1 -1
  17. package/dist/zest.it.js +104 -1
  18. package/dist/zest.it.js.map +1 -1
  19. package/dist/zest.it.min.js +1 -1
  20. package/dist/zest.ja.js +104 -1
  21. package/dist/zest.ja.js.map +1 -1
  22. package/dist/zest.ja.min.js +1 -1
  23. package/dist/zest.js +104 -1
  24. package/dist/zest.js.map +1 -1
  25. package/dist/zest.min.js +1 -1
  26. package/dist/zest.nl.js +104 -1
  27. package/dist/zest.nl.js.map +1 -1
  28. package/dist/zest.nl.min.js +1 -1
  29. package/dist/zest.pl.js +104 -1
  30. package/dist/zest.pl.js.map +1 -1
  31. package/dist/zest.pl.min.js +1 -1
  32. package/dist/zest.pt.js +104 -1
  33. package/dist/zest.pt.js.map +1 -1
  34. package/dist/zest.pt.min.js +1 -1
  35. package/dist/zest.ru.js +104 -1
  36. package/dist/zest.ru.js.map +1 -1
  37. package/dist/zest.ru.min.js +1 -1
  38. package/dist/zest.uk.js +104 -1
  39. package/dist/zest.uk.js.map +1 -1
  40. package/dist/zest.uk.min.js +1 -1
  41. package/dist/zest.zh.js +104 -1
  42. package/dist/zest.zh.js.map +1 -1
  43. package/dist/zest.zh.min.js +1 -1
  44. package/package.json +5 -4
  45. package/src/api/public-api.js +97 -0
  46. package/src/config/defaults.js +150 -0
  47. package/src/config/parser.js +104 -0
  48. package/src/core/categories.js +52 -0
  49. package/src/core/cookie-interceptor.js +116 -0
  50. package/src/core/dnt.js +56 -0
  51. package/src/core/known-trackers.js +168 -0
  52. package/src/core/pattern-matcher.js +96 -0
  53. package/src/core/script-blocker.js +308 -0
  54. package/src/core/storage-interceptor.js +169 -0
  55. package/src/i18n/lang-en.js +54 -0
  56. package/src/i18n/single/lang-de.js +55 -0
  57. package/src/i18n/single/lang-en.js +55 -0
  58. package/src/i18n/single/lang-es.js +55 -0
  59. package/src/i18n/single/lang-fr.js +55 -0
  60. package/src/i18n/single/lang-it.js +55 -0
  61. package/src/i18n/single/lang-ja.js +55 -0
  62. package/src/i18n/single/lang-nl.js +55 -0
  63. package/src/i18n/single/lang-pl.js +55 -0
  64. package/src/i18n/single/lang-pt.js +55 -0
  65. package/src/i18n/single/lang-ru.js +55 -0
  66. package/src/i18n/single/lang-uk.js +55 -0
  67. package/src/i18n/single/lang-zh.js +55 -0
  68. package/src/i18n/translations.js +546 -0
  69. package/src/index.js +377 -0
  70. package/src/integrations/consent-signals.js +71 -0
  71. package/src/storage/consent-store.js +177 -0
  72. package/src/storage/events.js +84 -0
  73. package/src/ui/banner.js +130 -0
  74. package/src/ui/modal.js +211 -0
  75. package/src/ui/styles.js +498 -0
  76. package/src/ui/widget.js +103 -0
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Banner - Main consent banner component
3
+ */
4
+
5
+ import { generateStyles } from './styles.js';
6
+ import { getCurrentConfig } from '../config/parser.js';
7
+
8
+ let bannerElement = null;
9
+ let shadowRoot = null;
10
+
11
+ /**
12
+ * Create the banner HTML
13
+ */
14
+ function createBannerHTML(config) {
15
+ const labels = config.labels.banner;
16
+ const position = config.position || 'bottom';
17
+
18
+ return `
19
+ <div class="zest-banner zest-banner--${position}" role="dialog" aria-modal="false" aria-label="${labels.title}">
20
+ <h2 class="zest-banner__title">${labels.title}</h2>
21
+ <p class="zest-banner__description">${labels.description}</p>
22
+ <div class="zest-banner__buttons">
23
+ <button type="button" class="zest-btn zest-btn--primary" data-action="accept-all">
24
+ ${labels.acceptAll}
25
+ </button>
26
+ <button type="button" class="zest-btn zest-btn--secondary" data-action="reject-all">
27
+ ${labels.rejectAll}
28
+ </button>
29
+ <button type="button" class="zest-btn zest-btn--ghost" data-action="settings">
30
+ ${labels.settings}
31
+ </button>
32
+ </div>
33
+ </div>
34
+ `;
35
+ }
36
+
37
+ /**
38
+ * Create and mount the banner
39
+ */
40
+ export function createBanner(callbacks = {}) {
41
+ if (bannerElement) {
42
+ return bannerElement;
43
+ }
44
+
45
+ const config = getCurrentConfig();
46
+
47
+ // Create host element
48
+ bannerElement = document.createElement('zest-banner');
49
+ bannerElement.setAttribute('data-theme', config.theme || 'light');
50
+
51
+ // Create shadow root
52
+ shadowRoot = bannerElement.attachShadow({ mode: 'open' });
53
+
54
+ // Add styles
55
+ const styleEl = document.createElement('style');
56
+ styleEl.textContent = generateStyles(config);
57
+ shadowRoot.appendChild(styleEl);
58
+
59
+ // Add banner HTML
60
+ const container = document.createElement('div');
61
+ container.innerHTML = createBannerHTML(config);
62
+ shadowRoot.appendChild(container.firstElementChild);
63
+
64
+ // Add event listeners
65
+ const banner = shadowRoot.querySelector('.zest-banner');
66
+
67
+ banner.addEventListener('click', (e) => {
68
+ const action = e.target.dataset.action;
69
+ if (!action) return;
70
+
71
+ switch (action) {
72
+ case 'accept-all':
73
+ callbacks.onAcceptAll?.();
74
+ break;
75
+ case 'reject-all':
76
+ callbacks.onRejectAll?.();
77
+ break;
78
+ case 'settings':
79
+ callbacks.onSettings?.();
80
+ break;
81
+ }
82
+ });
83
+
84
+ // Keyboard handling
85
+ banner.addEventListener('keydown', (e) => {
86
+ if (e.key === 'Escape') {
87
+ callbacks.onSettings?.();
88
+ }
89
+ });
90
+
91
+ // Mount to document
92
+ document.body.appendChild(bannerElement);
93
+
94
+ // Focus first button for accessibility
95
+ requestAnimationFrame(() => {
96
+ const firstButton = shadowRoot.querySelector('button');
97
+ firstButton?.focus();
98
+ });
99
+
100
+ return bannerElement;
101
+ }
102
+
103
+ /**
104
+ * Show the banner
105
+ */
106
+ export function showBanner(callbacks = {}) {
107
+ if (!bannerElement) {
108
+ createBanner(callbacks);
109
+ } else {
110
+ bannerElement.classList.remove('zest-hidden');
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Hide the banner
116
+ */
117
+ export function hideBanner() {
118
+ if (bannerElement) {
119
+ bannerElement.remove();
120
+ bannerElement = null;
121
+ shadowRoot = null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Check if banner is visible
127
+ */
128
+ export function isBannerVisible() {
129
+ return bannerElement !== null && document.body.contains(bannerElement);
130
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Modal - Settings modal component for category toggles
3
+ */
4
+
5
+ import { generateStyles } from './styles.js';
6
+ import { getCurrentConfig } from '../config/parser.js';
7
+ import { DEFAULT_CATEGORIES } from '../core/categories.js';
8
+
9
+ let modalElement = null;
10
+ let shadowRoot = null;
11
+ let currentSelections = {};
12
+
13
+ /**
14
+ * Create category toggle HTML
15
+ */
16
+ function createCategoryHTML(category, isChecked, isRequired) {
17
+ const disabled = isRequired ? 'disabled' : '';
18
+ const checked = isChecked ? 'checked' : '';
19
+
20
+ return `
21
+ <div class="zest-category">
22
+ <div class="zest-category__header">
23
+ <div class="zest-category__info">
24
+ <span class="zest-category__label">${category.label}</span>
25
+ <p class="zest-category__description">${category.description}</p>
26
+ </div>
27
+ <label class="zest-toggle">
28
+ <input
29
+ type="checkbox"
30
+ class="zest-toggle__input"
31
+ data-category="${category.id}"
32
+ ${checked}
33
+ ${disabled}
34
+ aria-label="${category.label}"
35
+ >
36
+ <span class="zest-toggle__slider"></span>
37
+ </label>
38
+ </div>
39
+ </div>
40
+ `;
41
+ }
42
+
43
+ /**
44
+ * Create the modal HTML
45
+ */
46
+ function createModalHTML(config, consent) {
47
+ const labels = config.labels.modal;
48
+ const categories = config.categories || DEFAULT_CATEGORIES;
49
+
50
+ const categoriesHTML = Object.values(categories)
51
+ .map(cat => createCategoryHTML(
52
+ cat,
53
+ consent[cat.id] ?? cat.default,
54
+ cat.required
55
+ ))
56
+ .join('');
57
+
58
+ const policyLink = config.policyUrl
59
+ ? `<a href="${config.policyUrl}" class="zest-link" target="_blank" rel="noopener">Privacy Policy</a>`
60
+ : '';
61
+
62
+ return `
63
+ <div class="zest-modal-overlay" role="dialog" aria-modal="true" aria-label="${labels.title}">
64
+ <div class="zest-modal">
65
+ <div class="zest-modal__header">
66
+ <h2 class="zest-modal__title">${labels.title}</h2>
67
+ <p class="zest-modal__description">${labels.description} ${policyLink}</p>
68
+ </div>
69
+ <div class="zest-modal__body">
70
+ ${categoriesHTML}
71
+ </div>
72
+ <div class="zest-modal__footer">
73
+ <button type="button" class="zest-btn zest-btn--primary" data-action="save">
74
+ ${labels.save}
75
+ </button>
76
+ <button type="button" class="zest-btn zest-btn--secondary" data-action="accept-all">
77
+ ${labels.acceptAll}
78
+ </button>
79
+ <button type="button" class="zest-btn zest-btn--ghost" data-action="reject-all">
80
+ ${labels.rejectAll}
81
+ </button>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ `;
86
+ }
87
+
88
+ /**
89
+ * Get current selections from toggles
90
+ */
91
+ function getSelections() {
92
+ if (!shadowRoot) return currentSelections;
93
+
94
+ const toggles = shadowRoot.querySelectorAll('.zest-toggle__input');
95
+ const selections = { essential: true };
96
+
97
+ toggles.forEach(toggle => {
98
+ const category = toggle.dataset.category;
99
+ if (category && category !== 'essential') {
100
+ selections[category] = toggle.checked;
101
+ }
102
+ });
103
+
104
+ return selections;
105
+ }
106
+
107
+ /**
108
+ * Create and show the modal
109
+ */
110
+ export function showModal(consent = {}, callbacks = {}) {
111
+ if (modalElement) {
112
+ return modalElement;
113
+ }
114
+
115
+ const config = getCurrentConfig();
116
+ currentSelections = { ...consent };
117
+
118
+ // Create host element
119
+ modalElement = document.createElement('zest-modal');
120
+ modalElement.setAttribute('data-theme', config.theme || 'light');
121
+
122
+ // Create shadow root
123
+ shadowRoot = modalElement.attachShadow({ mode: 'open' });
124
+
125
+ // Add styles
126
+ const styleEl = document.createElement('style');
127
+ styleEl.textContent = generateStyles(config);
128
+ shadowRoot.appendChild(styleEl);
129
+
130
+ // Add modal HTML
131
+ const container = document.createElement('div');
132
+ container.innerHTML = createModalHTML(config, consent);
133
+ shadowRoot.appendChild(container.firstElementChild);
134
+
135
+ // Add event listeners
136
+ const modal = shadowRoot.querySelector('.zest-modal-overlay');
137
+
138
+ // Button clicks
139
+ modal.addEventListener('click', (e) => {
140
+ const action = e.target.dataset.action;
141
+ if (!action) {
142
+ // Click on overlay background to close
143
+ if (e.target === modal) {
144
+ callbacks.onClose?.();
145
+ }
146
+ return;
147
+ }
148
+
149
+ switch (action) {
150
+ case 'save':
151
+ callbacks.onSave?.(getSelections());
152
+ break;
153
+ case 'accept-all':
154
+ callbacks.onAcceptAll?.();
155
+ break;
156
+ case 'reject-all':
157
+ callbacks.onRejectAll?.();
158
+ break;
159
+ }
160
+ });
161
+
162
+ // Keyboard handling
163
+ modal.addEventListener('keydown', (e) => {
164
+ if (e.key === 'Escape') {
165
+ callbacks.onClose?.();
166
+ }
167
+ });
168
+
169
+ // Track toggle changes
170
+ shadowRoot.querySelectorAll('.zest-toggle__input').forEach(toggle => {
171
+ toggle.addEventListener('change', () => {
172
+ currentSelections = getSelections();
173
+ });
174
+ });
175
+
176
+ // Mount to document
177
+ document.body.appendChild(modalElement);
178
+
179
+ // Trap focus
180
+ requestAnimationFrame(() => {
181
+ const firstButton = shadowRoot.querySelector('button');
182
+ firstButton?.focus();
183
+ });
184
+
185
+ return modalElement;
186
+ }
187
+
188
+ /**
189
+ * Hide the modal
190
+ */
191
+ export function hideModal() {
192
+ if (modalElement) {
193
+ modalElement.remove();
194
+ modalElement = null;
195
+ shadowRoot = null;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Check if modal is visible
201
+ */
202
+ export function isModalVisible() {
203
+ return modalElement !== null && document.body.contains(modalElement);
204
+ }
205
+
206
+ /**
207
+ * Get current modal selections
208
+ */
209
+ export function getModalSelections() {
210
+ return getSelections();
211
+ }